feat: replace SVG icons with Lucide icons in reference data pages

- Updated the "Nueva Sección" button in customs_sections, customs_warehouses, incoterms, invoice_types, material_types, payment_methods, pedimento_codes, pedimento_regimens, sectors, states, transport_modes, and transport_types pages to use the Plus icon from Lucide.
- Updated the "Actualizar" button in the same pages to use the RefreshCw icon from Lucide.
- Added a new edit dialog component for customs brokers with a comprehensive form for editing broker details, including validation and loading states.
This commit is contained in:
2025-11-24 16:43:53 -06:00
parent cdc3788a05
commit dcd42583d9
63 changed files with 1232 additions and 1810 deletions

View File

@@ -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")

View File

@@ -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

View File

@@ -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(

View File

@@ -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

View File

@@ -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")
)

View File

@@ -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

View File

@@ -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]] = (

View File

@@ -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,
)

View File

@@ -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,

View File

@@ -16,15 +16,21 @@ 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 {

View File

@@ -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<ClientProviderAddress, 'id' | 'client_id'> | null;
programs?: Omit<ClientProviderPrograms, 'id' | 'client_id'> | 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<ClientProviderAddress> | null;
programs?: Partial<ClientProviderPrograms> | 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<ClientProviderListResponse>(
`/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) =>

View File

@@ -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<CustomsBroker>(`/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<CustomsBroker>(`/v1/a76/customs-brokers/${brokerKey}?company_id=${companyId}`, data);
},
/**
* Actualiza la información de VU de un agente aduanal
*/

View File

@@ -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 = {

View File

@@ -15,9 +15,7 @@ export interface PedimentoDates {
eucan_date?: string | null;
original_date?: string | null;
start_date?: string | null;
end_date?: string | null;
capture_date?: string | null;
capture_time?: string | null;
end_date?: string | null;
}
export interface PedimentoPayments {

View File

@@ -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}
<div class="rounded-md bg-blue-50 border border-blue-200 p-3">
<div class="flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-blue-600"
>
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" />
</svg>
<Home class="h-4 w-4 text-blue-600" />
<div>
<p class="text-sm font-medium text-blue-900">
{companyStore.activeCompany.name}
@@ -426,26 +413,7 @@
</Button>
<Button type="submit" disabled={loading}>
{#if loading}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
Guardando...
{:else}
{isEdit ? 'Actualizar' : 'Crear'}

View File

@@ -3,6 +3,7 @@
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import { classesApi, type A76Class } from "$lib/api/dashboard/a76/classes";
import { companyStore } from "$lib/stores/company.svelte";
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
import CreateEditDialog from "./create-edit-dialog.svelte";
let {
@@ -80,21 +81,7 @@
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
<span class="sr-only">Abrir menú</span>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="1" />
<circle cx="12" cy="5" r="1" />
<circle cx="12" cy="19" r="1" />
</svg>
<EllipsisVertical size={16} />
</Button>
{/snippet}
</DropdownMenu.Trigger>
@@ -102,63 +89,15 @@
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
<DropdownMenu.Separator />
<DropdownMenu.Item onclick={handleEdit}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" />
<path d="m15 5 4 4" />
</svg>
<Pencil size={16} class="mr-2" />
Editar
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
{#if loading}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M3 6h18" />
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
</svg>
<Trash2 size={16} class="mr-2" />
{/if}
Eliminar
</DropdownMenu.Item>

View File

@@ -56,10 +56,10 @@ export function createColumns(onSuccess?: () => void): ColumnDef<ClientProvider>
const typeSnippet = createRawSnippet<[{ type: string | null | undefined }]>((getType) => {
const { type } = getType();
const displayType = type === 'client' ? 'Cliente' : type === 'provider' ? 'Proveedor' : type === 'both' ? 'Ambos' : 'N/A';
const colorClass = type === 'client' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300'
const colorClass = type === 'client' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300'
: type === 'provider' ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300'
: type === 'both' ? 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300'
: 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300';
: type === 'both' ? 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300'
: 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300';
return {
render: () => `<span class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${colorClass}">${displayType}</span>`
};
@@ -81,21 +81,21 @@ export function createColumns(onSuccess?: () => void): ColumnDef<ClientProvider>
}
},
{
accessorKey: "enabled_disabled",
accessorKey: "is_active",
header: "Estado",
cell: ({ row }) => {
const statusSnippet = createRawSnippet<[{ status: number | undefined }]>((getStatus) => {
const { status } = getStatus();
const isEnabled = status === 1;
const statusText = isEnabled ? 'Activo' : 'Inactivo';
const colorClass = isEnabled
? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300'
const colorClass = isEnabled
? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300'
: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300';
return {
render: () => `<span class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${colorClass}">${statusText}</span>`
};
});
return renderSnippet(statusSnippet, { status: row.original.enabled_disabled });
return renderSnippet(statusSnippet, { status: row.original.is_active });
}
},
{

View File

@@ -5,6 +5,7 @@
import { Label } from "$lib/components/ui/label";
import { clientsProvidersApi, type ClientProvider, type CreateClientProviderData, type UpdateClientProviderData } from "$lib/api/dashboard/a76/clients-providers";
import { companyStore } from "$lib/stores/company.svelte";
import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
@@ -24,7 +25,7 @@
domicile_fiscal: "",
foreign_tax_id: "",
client_or_provider: "client",
enabled_disabled: 1,
is_active: true,
// Address fields
street: "",
neighborhood: "",
@@ -51,7 +52,7 @@
domicile_fiscal: item.domicile_fiscal || "",
foreign_tax_id: item.foreign_tax_id || "",
client_or_provider: item.client_or_provider || "client",
enabled_disabled: item.enabled_disabled ?? 1,
is_active: item.is_active ?? true,
street: item.address?.street || "",
neighborhood: item.address?.neighborhood || "",
city: item.address?.city || "",
@@ -70,7 +71,7 @@
domicile_fiscal: "",
foreign_tax_id: "",
client_or_provider: "client",
enabled_disabled: 1,
is_active: true,
street: "",
neighborhood: "",
city: "",
@@ -107,7 +108,7 @@
domicile_fiscal: formData.domicile_fiscal || null,
foreign_tax_id: formData.foreign_tax_id || null,
client_or_provider: formData.client_or_provider as "client" | "provider" | "both" | null,
enabled_disabled: formData.enabled_disabled,
is_active: formData.is_active,
address: {
street: formData.street || null,
neighborhood: formData.neighborhood || null,
@@ -132,7 +133,7 @@
domicile_fiscal: formData.domicile_fiscal || null,
foreign_tax_id: formData.foreign_tax_id || null,
client_or_provider: formData.client_or_provider as "client" | "provider" | "both" | null,
enabled_disabled: formData.enabled_disabled,
is_active: formData.is_active,
address: {
street: formData.street || null,
neighborhood: formData.neighborhood || null,
@@ -185,7 +186,7 @@
domicile_fiscal: "",
foreign_tax_id: "",
client_or_provider: "client",
enabled_disabled: 1,
is_active: true,
street: "",
neighborhood: "",
city: "",
@@ -432,26 +433,7 @@
</Button>
<Button type="submit" disabled={loading}>
{#if loading}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{/if}
{isEditing ? "Guardar cambios" : "Crear"}
</Button>

View File

@@ -90,7 +90,7 @@
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
<DropdownMenu.Item onclick={handleEdit}>Editar</DropdownMenu.Item>
<DropdownMenu.Item onclick={handleToggleStatus} disabled={isToggling}>
{isToggling ? 'Cambiando...' : item.enabled_disabled === 1 ? 'Desactivar' : 'Activar'}
{isToggling ? 'Cambiando...' : item.is_active === true ? 'Desactivar' : 'Activar'}
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item class="text-destructive" onclick={handleDelete}>Eliminar</DropdownMenu.Item>

View File

@@ -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}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{/if}
Eliminar
</AlertDialog.Action>

View File

@@ -85,7 +85,7 @@
<div class="flex flex-col gap-1">
<span class="text-xs font-medium text-muted-foreground">Estado</span>
{#if item.enabled_disabled === 1}
{#if item.is_active === true}
<span class="inline-flex w-fit items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800 dark:bg-green-900 dark:text-green-300">
Activo
</span>

View File

@@ -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 @@
<DropdownMenu.Item onclick={handleViewDetails}>
Ver detalles
</DropdownMenu.Item>
<DropdownMenu.Item onclick={handleEdit}>
Editar
</DropdownMenu.Item>
<DropdownMenu.Item onclick={handleDelete} class="text-destructive">
Eliminar
</DropdownMenu.Item>
@@ -72,4 +81,5 @@
</DropdownMenu.Root>
<DetailsDialog bind:open={showDetailsDialog} {broker} />
<EditDialog bind:open={showEditDialog} {broker} {onSuccess} />
<DeleteDialog bind:open={showDeleteDialog} {broker} {onSuccess} />

View File

@@ -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}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
Eliminando...
{:else}
Eliminar

View File

@@ -0,0 +1,384 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as Dialog from "$lib/components/ui/dialog";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
import { customsBrokersApi, type CreateCustomsBrokerData, type CustomsBroker } from "$lib/api/dashboard/a76/customs-brokers";
import { companyStore } from "$lib/stores/company.svelte";
let {
open = $bindable(false),
broker,
onSuccess
}: {
open: boolean;
broker: CustomsBroker;
onSuccess?: () => void;
} = $props();
let formData = $state({
name: "",
type: "",
address: "",
postal_code: "",
city: "",
state: "",
phone: "",
fax: "",
email: "",
country: "",
tax_id: "",
personal_id: "",
position: "",
license: "",
company: "",
contact: ""
});
let loading = $state(false);
let error = $state<string | null>(null);
// Inicializar formulario cuando cambia el broker
$effect(() => {
if (open && broker) {
formData = {
name: broker.name || "",
type: broker.type || "",
address: broker.address || "",
postal_code: broker.postal_code || "",
city: broker.city || "",
state: broker.state || "",
phone: broker.phone || "",
fax: broker.fax || "",
email: broker.email || "",
country: broker.country || "",
tax_id: broker.tax_id || "",
personal_id: broker.personal_id || "",
position: broker.position || "",
license: broker.license || "",
company: broker.company || "",
contact: broker.contact || ""
};
}
});
async function handleSubmit(e: Event) {
e.preventDefault();
if (!companyStore.activeCompany) {
error = "No hay compañía seleccionada";
return;
}
loading = true;
error = null;
try {
const payload: CreateCustomsBrokerData = {
broker_key: broker.broker_key, // La clave no se edita
name: formData.name || null,
type: formData.type || null,
address: formData.address || null,
postal_code: formData.postal_code || null,
city: formData.city || null,
state: formData.state || null,
phone: formData.phone || null,
fax: formData.fax || null,
email: formData.email || null,
country: formData.country || null,
tax_id: formData.tax_id || null,
personal_id: formData.personal_id || null,
position: formData.position || null,
license: formData.license || null,
company: formData.company || null,
contact: formData.contact || null,
tenant_id: broker.tenant_id,
company_id: companyStore.activeCompany.id.toString()
};
const response = await customsBrokersApi.update(broker.broker_key, payload);
if (response.error) {
if (response.status === 401) {
error = 'Sesión expirada. Recargando página...';
setTimeout(() => {
window.location.reload();
}, 1500);
} else {
error = response.error;
}
return;
}
// Éxito
open = false;
if (onSuccess) {
onSuccess();
}
} catch (e) {
error = e instanceof Error ? e.message : "Error al guardar";
console.error("Error updating:", e);
} finally {
loading = false;
}
}
function handleOpenChange(newOpen: boolean) {
open = newOpen;
if (!newOpen) {
error = null;
}
}
</script>
<Dialog.Root bind:open onOpenChange={handleOpenChange}>
<Dialog.Content class="sm:max-w-[700px] max-h-[90vh] overflow-y-auto">
<Dialog.Header>
<Dialog.Title>Editar Agente Aduanal</Dialog.Title>
<Dialog.Description>
Modifica los datos del agente aduanal <span class="font-mono font-semibold">{broker?.broker_key}</span>.
</Dialog.Description>
</Dialog.Header>
<form onsubmit={handleSubmit} class="space-y-6">
{#if error}
<div class="rounded-lg border border-destructive bg-destructive/10 p-3 text-sm text-destructive">
{error}
</div>
{/if}
<!-- Información básica -->
<div class="space-y-4">
<h3 class="text-sm font-semibold">Información Básica</h3>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="edit-broker_key">Clave</Label>
<Input
id="edit-broker_key"
value={broker?.broker_key}
disabled
class="bg-muted"
/>
</div>
<div class="space-y-2">
<Label for="edit-type">Tipo</Label>
<Input
id="edit-type"
bind:value={formData.type}
placeholder="Tipo de agente"
maxlength={9}
disabled={loading}
/>
</div>
</div>
<div class="space-y-2">
<Label for="edit-name">Nombre</Label>
<Input
id="edit-name"
bind:value={formData.name}
placeholder="Nombre del agente aduanal"
maxlength={80}
disabled={loading}
/>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="edit-license">Patente</Label>
<Input
id="edit-license"
bind:value={formData.license}
placeholder="Número de patente"
maxlength={4}
disabled={loading}
/>
</div>
<div class="space-y-2">
<Label for="edit-company">Empresa</Label>
<Input
id="edit-company"
bind:value={formData.company}
placeholder="Empresa del agente"
maxlength={200}
disabled={loading}
/>
</div>
</div>
</div>
<!-- Información de contacto -->
<div class="space-y-4">
<h3 class="text-sm font-semibold">Información de Contacto</h3>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="edit-phone">Teléfono</Label>
<Input
id="edit-phone"
bind:value={formData.phone}
placeholder="Número telefónico"
maxlength={30}
disabled={loading}
/>
</div>
<div class="space-y-2">
<Label for="edit-fax">Fax</Label>
<Input
id="edit-fax"
bind:value={formData.fax}
placeholder="Número de fax"
maxlength={30}
disabled={loading}
/>
</div>
</div>
<div class="space-y-2">
<Label for="edit-email">Email</Label>
<Input
id="edit-email"
type="email"
bind:value={formData.email}
placeholder="correo@ejemplo.com"
maxlength={100}
disabled={loading}
/>
</div>
<div class="space-y-2">
<Label for="edit-contact">Contacto</Label>
<Input
id="edit-contact"
bind:value={formData.contact}
placeholder="Nombre del contacto"
maxlength={80}
disabled={loading}
/>
</div>
</div>
<!-- Dirección -->
<div class="space-y-4">
<h3 class="text-sm font-semibold">Dirección</h3>
<div class="space-y-2">
<Label for="edit-address">Dirección</Label>
<Input
id="edit-address"
bind:value={formData.address}
placeholder="Calle y número"
maxlength={1500}
disabled={loading}
/>
</div>
<div class="grid grid-cols-3 gap-4">
<div class="space-y-2">
<Label for="edit-postal_code">Código Postal</Label>
<Input
id="edit-postal_code"
bind:value={formData.postal_code}
placeholder="C.P."
maxlength={15}
disabled={loading}
/>
</div>
<div class="space-y-2">
<Label for="edit-city">Ciudad</Label>
<Input
id="edit-city"
bind:value={formData.city}
placeholder="Ciudad"
maxlength={30}
disabled={loading}
/>
</div>
<div class="space-y-2">
<Label for="edit-state">Estado</Label>
<Input
id="edit-state"
bind:value={formData.state}
placeholder="Estado"
maxlength={30}
disabled={loading}
/>
</div>
</div>
<div class="space-y-2">
<Label for="edit-country">País</Label>
<Input
id="edit-country"
bind:value={formData.country}
placeholder="País"
maxlength={3}
disabled={loading}
/>
</div>
</div>
<!-- Información fiscal -->
<div class="space-y-4">
<h3 class="text-sm font-semibold">Información Fiscal</h3>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="edit-tax_id">RFC</Label>
<Input
id="edit-tax_id"
bind:value={formData.tax_id}
placeholder="RFC"
maxlength={30}
disabled={loading}
/>
</div>
<div class="space-y-2">
<Label for="edit-personal_id">CURP</Label>
<Input
id="edit-personal_id"
bind:value={formData.personal_id}
placeholder="CURP"
maxlength={20}
disabled={loading}
/>
</div>
</div>
<div class="space-y-2">
<Label for="edit-position">Posición</Label>
<Input
id="edit-position"
bind:value={formData.position}
placeholder="Cargo o posición"
maxlength={30}
disabled={loading}
/>
</div>
</div>
<Dialog.Footer>
<Button type="button" variant="outline" onclick={() => (open = false)} disabled={loading}>
Cancelar
</Button>
<Button type="submit" disabled={loading}>
{#if loading}
<div class="flex items-center gap-2">
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary-foreground border-t-transparent"></div>
Guardando...
</div>
{:else}
Guardar Cambios
{/if}
</Button>
</Dialog.Footer>
</form>
</Dialog.Content>
</Dialog.Root>

View File

@@ -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 })}
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
<span class="sr-only">Abrir menú</span>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="1" />
<circle cx="12" cy="5" r="1" />
<circle cx="12" cy="19" r="1" />
</svg>
<EllipsisVertical class="h-4 w-4" />
</Button>
{/snippet}
</DropdownMenu.Trigger>
@@ -84,63 +71,15 @@
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
<DropdownMenu.Separator />
<DropdownMenu.Item onclick={handleEdit}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" />
<path d="m15 5 4 4" />
</svg>
<Pencil class="mr-2 h-4 w-4" />
Editar
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
{#if loading}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M3 6h18" />
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
</svg>
<Trash2 class="mr-2 h-4 w-4" />
{/if}
Eliminar
</DropdownMenu.Item>

View File

@@ -30,8 +30,6 @@
original_date: datesData.original_date ? datesData.original_date.substring(0, 10) : '',
start_date: datesData.start_date ? datesData.start_date.substring(0, 10) : '',
end_date: datesData.end_date ? datesData.end_date.substring(0, 10) : '',
capture_date: datesData.capture_date ? datesData.capture_date.substring(0, 10) : '',
capture_time: datesData.capture_time || ''
};
}
} else {
@@ -48,7 +46,6 @@
original_date: '',
start_date: '',
end_date: '',
capture_date: '',
capture_time: ''
};
}
@@ -163,27 +160,7 @@
type="date"
bind:value={formData.end_date}
/>
</div>
<!-- Fecha de Captura -->
<div class="space-y-2">
<Label for="capture_date">Fecha de Captura</Label>
<Input
id="capture_date"
type="date"
bind:value={formData.capture_date}
/>
</div>
<!-- Hora de Captura -->
<div class="space-y-2">
<Label for="capture_time">Hora de Captura</Label>
<Input
id="capture_time"
type="time"
bind:value={formData.capture_time}
/>
</div>
</div>
</div>
</div>
</Card.Content>

View File

@@ -5,26 +5,175 @@
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(),
pedimentoCodes = []
pedimentoCodes = [],
customsSections = [],
customsBrokers = [],
clients = [],
codePedimentoRegimens = []
}: {
pedimento: Pedimento | null;
formData?: any;
pedimentoCodes?: PedimentoCode[];
customsSections?: CustomsSection[];
customsBrokers?: CustomsBroker[];
clients?: ClientProvider[];
codePedimentoRegimens?: CodePedimentoRegimen[];
} = $props();
// Debug: verificar que los datos llegan
$effect(() => {
console.log('pedimentoCodes:', pedimentoCodes.length, 'items');
// 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 || '',
@@ -41,6 +190,13 @@
};
}
// 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' },
@@ -85,6 +241,8 @@
placeholder="23"
maxlength={2}
class="text-center"
disabled
readonly
/>
</div>
@@ -92,15 +250,28 @@
<div class="pb-2 text-2xl font-semibold text-muted-foreground">-</div>
<!-- Aduana -->
<div class="space-y-2 w-16">
<div class="space-y-2 w-20">
<Label for="customs_office">Aduana</Label>
<Input
id="customs_office"
bind:value={formData.customs_office}
placeholder="01"
maxlength={2}
class="text-center"
/>
<Select.Root
type="single"
value={formData.customs_office || ''}
onValueChange={(v: string) => formData.customs_office = v ?? ''}
>
<Select.Trigger class="w-full">
<span class="truncate">
{formData.customs_office || 'Sel...'}
</span>
</Select.Trigger>
<Select.Content class="max-w-[300px] max-h-[300px]">
{#each customsSections as section}
<Select.Item value={section.customs_code}>
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={`${section.customs_code} - ${section.section_name}`}>
{section.customs_code} - {section.section_name}
</span>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Separador -->
@@ -109,13 +280,26 @@
<!-- Patente -->
<div class="space-y-2 w-24">
<Label for="license">Patente</Label>
<Input
id="license"
bind:value={formData.license}
placeholder="1234"
maxlength={4}
class="text-center"
/>
<Select.Root
type="single"
value={formData.license || ''}
onValueChange={(v: string) => formData.license = v ?? ''}
>
<Select.Trigger class="w-full">
<span class="truncate">
{formData.license || 'Sel...'}
</span>
</Select.Trigger>
<Select.Content class="max-w-[300px] max-h-[300px]">
{#each customsBrokers as broker}
<Select.Item value={broker.license}>
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={`${broker.broker_key} - ${broker.name || ''}`}>
{broker.broker_key} - {broker.name || ''}
</span>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Separador -->
@@ -137,29 +321,24 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- ID del Cliente -->
<div class="space-y-2">
<Label for="client_id">ID del Cliente</Label>
<Input
id="client_id"
type="number"
bind:value={formData.client_id}
placeholder="Ej: 123"
/>
</div>
<!-- Tipo de Operación -->
<div class="space-y-2">
<Label for="operation_type">Tipo de Operación</Label>
<Label for="client_id">Cliente</Label>
<Select.Root
type="single"
value={String(formData.operation_type ?? '')}
onValueChange={(v: string) => formData.operation_type = v ? Number(v) : null}
value={String(formData.client_id ?? '')}
onValueChange={(v: string) => formData.client_id = v ? Number(v) : null}
>
<Select.Trigger class="w-full">
{operationOptions.find(o => o.value === formData.operation_type)?.label || 'Seleccionar...'}
<span class="truncate">
{clients.find(c => c.id === formData.client_id)?.name || 'Seleccionar cliente...'}
</span>
</Select.Trigger>
<Select.Content>
{#each operationOptions as option}
<Select.Item value={String(option.value)} label={option.label} />
<Select.Content class="max-h-[300px]">
{#each clients as client}
<Select.Item value={String(client.id)} label={client.name}>
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={client.name}>
{client.name}
</span>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
@@ -176,40 +355,81 @@
/>
</div>
<!-- Clave del Pedimento -->
<div class="space-y-2">
<Label for="pedimento_code">Clave del Pedimento</Label>
<Select.Root
type="single"
value={formData.pedimento_code || ''}
onValueChange={(v: string) => formData.pedimento_code = v ?? ''}
>
<Select.Trigger class="w-full">
<span class="truncate">
{pedimentoCodes.find(o => o.code === formData.pedimento_code)?.code || 'Seleccionar...'}
</span>
</Select.Trigger>
<Select.Content class="max-w-[600px]">
{#each pedimentoCodes as code}
<Select.Item value={code.code}>
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={`${code.code} - ${code.description}`}>
{code.code} - {code.description}
</span>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
</div>
<!-- Régimen -->
<div class="space-y-2">
<Label for="regime">Régimen</Label>
<Input
id="regime"
bind:value={formData.regime}
placeholder="Ej: IMD"
/>
</div>
<!-- Fila: Clave (2), Régimen (3), Tipo de Operación (11) -->
<div class="flex items-end gap-2">
<!-- Clave del Pedimento -->
<div class="space-y-2 w-20">
<Label for="pedimento_code">Clave</Label>
<Select.Root
type="single"
value={formData.pedimento_code || ''}
onValueChange={(v: string) => formData.pedimento_code = v ?? ''}
>
<Select.Trigger class="w-full">
<span class="truncate">
{pedimentoCodes.find(o => o.code === formData.pedimento_code)?.code || 'Sel...'}
</span>
</Select.Trigger>
<Select.Content class="max-w-[200px]">
{#each pedimentoCodes as code}
<Select.Item value={code.code}>
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={`${code.code} - ${code.description}`}>
{code.code} - {code.description}
</span>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Régimen -->
<div class="space-y-2 w-24">
<Label for="regime">Régimen</Label>
<Select.Root
type="single"
value={formData.regime || ''}
onValueChange={(v: string) => formData.regime = v ?? ''}
>
<Select.Trigger class="w-full">
<span class="truncate">
{formData.regime || 'Sel...'}
</span>
</Select.Trigger>
<Select.Content class="max-w-[200px]">
{#each filteredRegimens() as regimen}
<Select.Item value={regimen.code}>
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={regimen.code}>
{regimen.code}
</span>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Tipo de Operación -->
<div class="space-y-2 flex-1">
<Label for="operation_type">Tipo de Operación</Label>
<Select.Root
type="single"
value={String(formData.operation_type ?? '')}
onValueChange={(v: string) => formData.operation_type = v ? Number(v) : null}
>
<Select.Trigger class="w-full">
{operationOptions.find(o => o.value === formData.operation_type)?.label || 'Seleccionar...'}
</Select.Trigger>
<Select.Content>
{#each filteredOperationTypes() as option}
<Select.Item value={String(option.value)} label={option.label} />
{/each}
</Select.Content>
</Select.Root>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- Estado -->
<div class="space-y-2">

View File

@@ -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),

View File

@@ -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}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{/if}
Eliminar
</AlertDialog.Action>

View File

@@ -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),

View File

@@ -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),

View File

@@ -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}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{/if}
Eliminar
</AlertDialog.Action>

View File

@@ -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}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{/if}
Eliminar
</AlertDialog.Action>

View File

@@ -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}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{/if}
Eliminar
</AlertDialog.Action>

View File

@@ -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 @@
</Button>
<Button type="submit" disabled={loading}>
{#if loading}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{/if}
{isEditing ? "Guardar cambios" : "Crear"}
</Button>

View File

@@ -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}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{/if}
Eliminar
</AlertDialog.Action>

View File

@@ -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}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{/if}
Eliminar
</AlertDialog.Action>

View File

@@ -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 @@
<div class="relative w-full h-full flex flex-col items-center justify-center gap-8">
<!-- Ícono principal - Escudo con documento -->
<div class="relative">
<svg class="w-48 h-48 text-blue-600 dark:text-blue-400 opacity-90" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
<FileText class="w-48 h-48 text-blue-600 dark:text-blue-400 opacity-90" strokeWidth={1.5} />
<!-- Escudo de protección superpuesto -->
<div class="absolute -bottom-2 -right-2 bg-white dark:bg-slate-800 rounded-full p-2 shadow-lg">
<svg class="w-12 h-12 text-green-600 dark:text-green-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M2.166 4.999A11.954 11.954 0 0010 1.944 11.954 11.954 0 0017.834 5c.11.65.166 1.32.166 2.001 0 5.225-3.34 9.67-8 11.317C5.34 16.67 2 12.225 2 7c0-.682.057-1.35.166-2.001zm11.541 3.708a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
</svg>
<ShieldCheck class="w-12 h-12 text-green-600 dark:text-green-400" fill="currentColor" />
</div>
</div>

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import * as Card from "$lib/components/ui/card";
import { FileText, LayoutGrid, Package } from 'lucide-svelte';
</script>
<div class="space-y-6">
@@ -52,78 +53,27 @@
</Card.Header>
<Card.Content>
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<a
href="/dashboard/reference_data/code_pedimento_regimens"
class="flex flex-col items-center justify-center rounded-lg border p-6 hover:bg-accent transition-colors"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mb-2"
>
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14 2 14 8 20 8" />
<line x1="16" x2="8" y1="13" y2="13" />
<line x1="16" x2="8" y1="17" y2="17" />
<polyline points="10 9 9 9 8 9" />
</svg>
<span class="font-medium">Código Pedimento - Regímenes</span>
<span class="text-xs text-muted-foreground">Gestionar relaciones</span>
</a>
<div
class="flex flex-col items-center justify-center rounded-lg border p-6 opacity-50 cursor-not-allowed"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mb-2"
>
<rect width="18" height="18" x="3" y="3" rx="2" ry="2" />
<line x1="3" x2="21" y1="9" y2="9" />
<line x1="9" x2="9" y1="21" y2="9" />
</svg>
<span class="font-medium">Catálogo de Tipos</span>
<span class="text-xs text-muted-foreground">Próximamente</span>
</div>
<div
class="flex flex-col items-center justify-center rounded-lg border p-6 opacity-50 cursor-not-allowed"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mb-2"
>
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
<polyline points="3.27 6.96 12 12.01 20.73 6.96" />
<line x1="12" x2="12" y1="22.08" y2="12" />
</svg>
<span class="font-medium">Reportes</span>
<span class="text-xs text-muted-foreground">Próximamente</span>
</div>
<a
href="/dashboard/reference_data/code_pedimento_regimens"
class="flex flex-col items-center justify-center rounded-lg border p-6 hover:bg-accent transition-colors"
>
<FileText class="mb-2" size={24} />
<span class="font-medium">Código Pedimento - Regímenes</span>
<span class="text-xs text-muted-foreground">Gestionar relaciones</span>
</a> <div
class="flex flex-col items-center justify-center rounded-lg border p-6 opacity-50 cursor-not-allowed"
>
<LayoutGrid class="mb-2" size={24} />
<span class="font-medium">Catálogo de Tipos</span>
<span class="text-xs text-muted-foreground">Próximamente</span>
</div> <div
class="flex flex-col items-center justify-center rounded-lg border p-6 opacity-50 cursor-not-allowed"
>
<Package class="mb-2" size={24} />
<span class="font-medium">Reportes</span>
<span class="text-xs text-muted-foreground">Próximamente</span>
</div>
</Card.Content>
</Card.Root>
</div>
</div>
</Card.Content>
</Card.Root>
</div>

View File

@@ -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: [],

View File

@@ -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<string>($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 @@
</p>
</div>
<Button onclick={handleCreateClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
<Plus class="mr-2" size={16} />
Nuevo Cliente/Proveedor
</Button>
</div>
@@ -216,25 +224,23 @@
{/if}
</Card.Description>
</div>
<Button variant="outline" onclick={reloadData}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
</svg>
<div class="flex items-center gap-2">
<Select.Root type="single" value={selectedType} onValueChange={handleTypeChange}>
<Select.Trigger class="w-[180px]">
{selectedType === 'both' ? 'Todos' : selectedType === 'client' ? 'Clientes' : 'Proveedores'}
</Select.Trigger>
<Select.Content>
<Select.Item value="both">Todos</Select.Item>
<Select.Item value="client">Clientes</Select.Item>
<Select.Item value="provider">Proveedores</Select.Item>
</Select.Content>
</Select.Root>
<Button variant="outline" onclick={reloadData}>
<RefreshCw class="mr-2" size={16} />
Actualizar
</Button>
</div>
</div>
</Card.Header>
<Card.Content>
<!-- TanStack DataTable con Infinite Scroll -->

View File

@@ -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 @@
</p>
</div>
<Button onclick={handleCreateClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
<Plus class="mr-2" size={16} />
Nuevo Agente Aduanal
</Button>
</div>
@@ -226,44 +213,15 @@
Buscando...
</div>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.3-4.3" />
</svg>
<Search class="mr-2" size={16} />
Buscar
{/if}
</Button>
{#if searchedBroker}
<Button type="button" variant="outline" onclick={reloadData}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M3 6h18" />
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
</svg>
Limpiar
</Button>
{#if searchedBroker}
<Button type="button" variant="outline" onclick={reloadData}>
<Trash2 class="mr-2" size={16} />
Limpiar
</Button>
{/if}
</div>
</div>

View File

@@ -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 @@
</p>
</div>
<Button onclick={handleCreateClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
<Plus class="mr-2" size={16} />
Nueva Clase
</Button>
</div>
@@ -305,38 +292,11 @@
<div class="flex items-end gap-2 md:col-span-2">
<Button type="submit" disabled={loading} class="flex-1">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" />
</svg>
<Filter class="mr-2" size={16} />
Filtrar
</Button>
<Button type="button" variant="outline" onclick={clearFilters} disabled={loading}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M3 6h18" />
<path d="m19 6-2 14a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2L5 6" />
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
</svg>
<Trash2 size={16} />
</Button>
</div>
</form>
@@ -364,21 +324,7 @@
</Card.Description>
</div>
<Button variant="outline" onclick={reloadData}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
</svg>
<RefreshCw class="mr-2" size={16} />
Actualizar
</Button>
</div>

View File

@@ -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 @@
</p>
</div>
<Button onclick={handleCreateClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
<Plus class="mr-2" size={16} />
Nuevo Pedimento
</Button>
</div>
@@ -322,38 +309,11 @@
<div class="flex items-end gap-2">
<Button type="submit" disabled={loading} class="flex-1">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" />
</svg>
<Filter class="mr-2" size={16} />
Filtrar
</Button>
<Button type="button" variant="outline" onclick={clearFilters} disabled={loading}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M3 6h18" />
<path d="m19 6-2 14a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2L5 6" />
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
</svg>
<Trash2 size={16} />
</Button>
</div>
</form>
@@ -381,21 +341,7 @@
</Card.Description>
</div>
<Button variant="outline" onclick={reloadData}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
</svg>
<RefreshCw class="mr-2" size={16} />
Actualizar
</Button>
</div>

View File

@@ -9,6 +9,13 @@ 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',
@@ -17,16 +24,63 @@ export const load: PageServerLoad = async ({ params, cookies, fetch }) => {
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 = await pedimentoCodesPromise;
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,
pedimentoCodes: pedimentoCodes.items || []
pedimentoCodes: pedimentoCodes.items || [],
customsSections: customsSections.items || [],
customsBrokers: customsBrokers.items || [],
clients: clients.items || [],
codePedimentoRegimens: codePedimentoRegimens.items || []
};
}
@@ -36,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(
@@ -63,15 +112,30 @@ export const load: PageServerLoad = async ({ params, cookies, fetch }) => {
const pedimento = await response.json();
// Cargar pedimento_codes
const pedimentoCodesResponse = await pedimentoCodesPromise;
// 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,
pedimentoCodes: pedimentoCodes.items || []
pedimentoCodes: pedimentoCodes.items || [],
customsSections: customsSections.items || [],
customsBrokers: customsBrokers.items || [],
clients: clients.items || [],
codePedimentoRegimens: codePedimentoRegimens.items || []
};
} catch (e) {
console.error('Error loading pedimento:', e);

View File

@@ -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)
@@ -19,12 +31,20 @@
// 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';
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;
@@ -133,8 +153,7 @@
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 ||
datesFormData.capture_date || datesFormData.capture_time;
datesFormData.start_date || datesFormData.end_date;
if (hasDateValue) {
payload.pedimento_dates = {
@@ -148,8 +167,6 @@
original_date: datesFormData.original_date || null,
start_date: datesFormData.start_date || null,
end_date: datesFormData.end_date || null,
capture_date: datesFormData.capture_date || null,
capture_time: datesFormData.capture_time || null
};
}
}
@@ -267,20 +284,7 @@
<div class="space-y-1">
<div class="flex items-center gap-3">
<Button variant="ghost" size="icon" onclick={handleBack}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m12 19-7-7 7-7" />
<path d="M19 12H5" />
</svg>
<ArrowLeft size={20} />
</Button>
<h1 class="text-3xl font-bold tracking-tight">
{#if data.isCreate}
@@ -312,21 +316,7 @@
<!-- Alertas globales -->
{#if error}
<Alert.Root variant="destructive">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="10" />
<line x1="12" x2="12" y1="8" y2="12" />
<line x1="12" x2="12.01" y1="16" y2="16" />
</svg>
<CircleAlert size={16} />
<Alert.Title>Error</Alert.Title>
<Alert.Description>{error}</Alert.Description>
</Alert.Root>
@@ -334,217 +324,105 @@
{#if success}
<Alert.Root>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z" />
<path d="m9 12 2 2 4-4" />
</svg>
<CircleCheck size={16} />
<Alert.Title>Éxito</Alert.Title>
<Alert.Description>Todos los cambios se guardaron correctamente</Alert.Description>
</Alert.Root>
{/if}
<!-- Tabs Navigation -->
<Tabs.Root bind:value={activeTab} class="space-y-4">
<Tabs.List class="grid w-full grid-cols-5">
<Tabs.Trigger value="general" disabled={false}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z" />
<polyline points="14 2 14 8 20 8" />
</svg>
General
</Tabs.Trigger>
<Tabs.Trigger value="dates" disabled={false}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<rect width="18" height="18" x="3" y="4" rx="2" ry="2" />
<line x1="16" x2="16" y1="2" y2="6" />
<line x1="8" x2="8" y1="2" y2="6" />
<line x1="3" x2="21" y1="10" y2="10" />
</svg>
Fechas
</Tabs.Trigger>
<Tabs.Trigger value="payments" disabled={false}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<rect width="20" height="14" x="2" y="5" rx="2" />
<line x1="2" x2="22" y1="10" y2="10" />
</svg>
Pagos
</Tabs.Trigger>
<Tabs.Trigger value="transport" disabled={false}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2" />
<path d="M15 18H9" />
<path d="M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14" />
<circle cx="17" cy="18" r="2" />
<circle cx="7" cy="18" r="2" />
</svg>
Transporte
</Tabs.Trigger>
<Tabs.Trigger value="validation" disabled={false}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10" />
<path d="m9 12 2 2 4-4" />
</svg>
Validación
</Tabs.Trigger>
</Tabs.List>
<!-- Contenido de las tabs con padding inferior para el footer flotante -->
<div class="pb-48">
<Tabs.Root bind:value={activeTab} class="space-y-4">
<Tabs.Content value="general">
<GeneralTabForm
pedimento={data.pedimento}
bind:formData={generalFormData}
pedimentoCodes={data.pedimentoCodes || []}
customsSections={data.customsSections || []}
customsBrokers={data.customsBrokers || []}
clients={data.clients || []}
codePedimentoRegimens={data.codePedimentoRegimens || []}
/>
</Tabs.Content>
<Tabs.Content value="general">
<GeneralTabForm
pedimento={data.pedimento}
bind:formData={generalFormData}
pedimentoCodes={data.pedimentoCodes || []}
/>
</Tabs.Content>
<Tabs.Content value="dates">
<DatesTabForm
pedimento={data.pedimento}
bind:formData={datesFormData}
bind:exists={datesExists}
/>
</Tabs.Content>
<Tabs.Content value="dates">
<DatesTabForm
pedimento={data.pedimento}
bind:formData={datesFormData}
bind:exists={datesExists}
/>
</Tabs.Content>
<Tabs.Content value="payments">
<PaymentsTabForm
pedimento={data.pedimento}
bind:formData={paymentsFormData}
bind:exists={paymentsExists}
/>
</Tabs.Content>
<Tabs.Content value="payments">
<PaymentsTabForm
pedimento={data.pedimento}
bind:formData={paymentsFormData}
bind:exists={paymentsExists}
/>
</Tabs.Content>
<Tabs.Content value="transport">
<TransportTabForm
pedimento={data.pedimento}
bind:formData={transportFormData}
bind:exists={transportExists}
/>
</Tabs.Content>
<Tabs.Content value="transport">
<TransportTabForm
pedimento={data.pedimento}
bind:formData={transportFormData}
bind:exists={transportExists}
/>
</Tabs.Content>
<Tabs.Content value="validation">
<ValidationTabForm
pedimento={data.pedimento}
bind:formData={validationFormData}
bind:exists={validationExists}
/>
</Tabs.Content>
</Tabs.Root>
<!-- Botón de guardar global -->
<Card.Root>
<Card.Content class="pt-6">
<div class="flex justify-end gap-3">
<Button type="button" variant="outline" onclick={handleBack} disabled={saving}>
Cancelar
</Button>
<Button onclick={handleSaveAll} disabled={saving}>
{#if saving}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
Guardando todos los cambios...
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
<polyline points="17 21 17 13 7 13 7 21" />
<polyline points="7 3 7 8 15 8" />
</svg>
Guardar Todos los Cambios
{/if}
</Button>
</div>
</Card.Content>
</Card.Root>
<Tabs.Content value="validation">
<ValidationTabForm
pedimento={data.pedimento}
bind:formData={validationFormData}
bind:exists={validationExists}
/>
</Tabs.Content>
</Tabs.Root>
</div>
</div>
<!-- Footer fijo en la parte inferior - Fuera del contenedor principal -->
<div class="fixed bottom-0 inset-x-0 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/80 border-t shadow-lg z-50 group-has-[[data-sidebar]]/sidebar-wrapper:left-[var(--sidebar-width)]">
<div class="px-4 py-4 space-y-4 max-w-[1400px] mx-auto">
<!-- Tabs Navigation -->
<Tabs.Root bind:value={activeTab}>
<Tabs.List class="grid w-full grid-cols-5">
<Tabs.Trigger value="general" disabled={false}>
<FileText size={16} class="mr-2" />
General
</Tabs.Trigger>
<Tabs.Trigger value="dates" disabled={false}>
<Calendar size={16} class="mr-2" />
Fechas
</Tabs.Trigger>
<Tabs.Trigger value="payments" disabled={false}>
<CreditCard size={16} class="mr-2" />
Pagos
</Tabs.Trigger>
<Tabs.Trigger value="transport" disabled={false}>
<Truck size={16} class="mr-2" />
Transporte
</Tabs.Trigger>
<Tabs.Trigger value="validation" disabled={false}>
<ShieldCheck size={16} class="mr-2" />
Validación
</Tabs.Trigger>
</Tabs.List>
</Tabs.Root>
<!-- Botones de acción -->
<div class="flex justify-end gap-3">
<Button type="button" variant="outline" onclick={handleBack} disabled={saving}>
Cancelar
</Button>
<Button onclick={handleSaveAll} disabled={saving}>
{#if saving}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
Guardando todos los cambios...
{:else}
<Save size={16} class="mr-2" />
Guardar Todos los Cambios
{/if}
</Button>
</div>
</div>
</div>

View File

@@ -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 @@
</p>
</div>
<Button onclick={handleCreateClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
<Plus class="mr-2" size={16} />
Nuevo Registro
</Button>
</div>
@@ -160,21 +147,7 @@
</Card.Description>
</div>
<Button variant="outline" onclick={reloadData}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
</svg>
<RefreshCw class="mr-2" size={16} />
Actualizar
</Button>
</div>

View File

@@ -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 @@
</p>
</div>
<Button onclick={handleCreateClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
<Plus class="mr-2" size={16} />
Nuevo Contenedor
</Button>
</div>
@@ -160,21 +147,7 @@
</Card.Description>
</div>
<Button variant="outline" onclick={reloadData}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
</svg>
<RefreshCw class="mr-2" size={16} />
Actualizar
</Button>
</div>

View File

@@ -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 @@
</p>
</div>
<Button onclick={handleCreateClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
<Plus class="mr-2" size={16} />
Nuevo País
</Button>
</div>
@@ -160,21 +147,7 @@
</Card.Description>
</div>
<Button variant="outline" onclick={reloadData}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
</svg>
<RefreshCw class="mr-2" size={16} />
Actualizar
</Button>
</div>

View File

@@ -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 @@
</p>
</div>
<Button onclick={handleCreateClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
Nueva Moneda
<Plus class="mr-2" size={16} />
Nuevo Tipo de Moneda
</Button>
</div>
@@ -160,21 +147,7 @@
</Card.Description>
</div>
<Button variant="outline" onclick={reloadData}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
</svg>
<RefreshCw class="mr-2" size={16} />
Actualizar
</Button>
</div>

View File

@@ -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 @@
</p>
</div>
<Button onclick={handleCreateClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
<Plus class="mr-2" size={16} />
Nueva Sección
</Button>
</div>
@@ -160,21 +147,7 @@
</Card.Description>
</div>
<Button variant="outline" onclick={reloadData}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
</svg>
<RefreshCw class="mr-2" size={16} />
Actualizar
</Button>
</div>

View File

@@ -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 @@
</p>
</div>
<Button onclick={handleCreateClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
<Plus class="mr-2" size={16} />
Nuevo Recinto
</Button>
</div>
@@ -160,21 +147,7 @@
</Card.Description>
</div>
<Button variant="outline" onclick={reloadData}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
</svg>
<RefreshCw class="mr-2" size={16} />
Actualizar
</Button>
</div>

View File

@@ -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 @@
</p>
</div>
<Button onclick={handleCreateClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
<Plus class="mr-2" size={16} />
Nuevo Incoterm
</Button>
</div>
@@ -160,21 +147,7 @@
</Card.Description>
</div>
<Button variant="outline" onclick={reloadData}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
</svg>
<RefreshCw class="mr-2" size={16} />
Actualizar
</Button>
</div>

View File

@@ -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 @@
</p>
</div>
<Button onclick={handleCreateClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
<Plus class="mr-2" size={16} />
Nuevo Tipo de Factura
</Button>
</div>
@@ -160,21 +147,7 @@
</Card.Description>
</div>
<Button variant="outline" onclick={reloadData}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
</svg>
<RefreshCw class="mr-2" size={16} />
Actualizar
</Button>
</div>

View File

@@ -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 @@
</p>
</div>
<Button onclick={handleCreateClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
<Plus class="mr-2" size={16} />
Nuevo Tipo de Material
</Button>
</div>
@@ -160,21 +147,7 @@
</Card.Description>
</div>
<Button variant="outline" onclick={reloadData}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
</svg>
<RefreshCw class="mr-2" size={16} />
Actualizar
</Button>
</div>

View File

@@ -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 @@
</p>
</div>
<Button onclick={handleCreateClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
<Plus class="mr-2" size={16} />
Nuevo Método de Pago
</Button>
</div>
@@ -160,21 +147,7 @@
</Card.Description>
</div>
<Button variant="outline" onclick={reloadData}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
</svg>
<RefreshCw class="mr-2" size={16} />
Actualizar
</Button>
</div>

View File

@@ -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 @@
</p>
</div>
<Button onclick={handleCreateClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
Nueva Clave de Pedimento
<Plus class="mr-2" size={16} />
Nuevo Código de Pedimento
</Button>
</div>
@@ -160,21 +147,7 @@
</Card.Description>
</div>
<Button variant="outline" onclick={reloadData}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
</svg>
<RefreshCw class="mr-2" size={16} />
Actualizar
</Button>
</div>

View File

@@ -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 @@
</p>
</div>
<Button onclick={handleCreateClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
<Plus class="mr-2" size={16} />
Nuevo Régimen de Pedimento
</Button>
</div>
@@ -160,21 +147,7 @@
</Card.Description>
</div>
<Button variant="outline" onclick={reloadData}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
</svg>
<RefreshCw class="mr-2" size={16} />
Actualizar
</Button>
</div>

View File

@@ -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 @@
</p>
</div>
<Button onclick={handleCreateClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
<Plus class="mr-2" size={16} />
Nuevo Sector
</Button>
</div>
@@ -160,21 +147,7 @@
</Card.Description>
</div>
<Button variant="outline" onclick={reloadData}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
</svg>
<RefreshCw class="mr-2" size={16} />
Actualizar
</Button>
</div>

View File

@@ -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 @@
</p>
</div>
<Button onclick={handleCreateClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
<Plus class="mr-2" size={16} />
Nuevo Estado
</Button>
</div>
@@ -160,21 +147,7 @@
</Card.Description>
</div>
<Button variant="outline" onclick={reloadData}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
</svg>
<RefreshCw class="mr-2" size={16} />
Actualizar
</Button>
</div>

View File

@@ -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 @@
</p>
</div>
<Button onclick={handleCreateClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
<Plus class="mr-2" size={16} />
Nuevo Modo de Transporte
</Button>
</div>
@@ -160,21 +147,7 @@
</Card.Description>
</div>
<Button variant="outline" onclick={reloadData}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
</svg>
<RefreshCw class="mr-2" size={16} />
Actualizar
</Button>
</div>

View File

@@ -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 @@
</p>
</div>
<Button onclick={handleCreateClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
<Plus class="mr-2" size={16} />
Nuevo Tipo de Transporte
</Button>
</div>
@@ -160,21 +147,7 @@
</Card.Description>
</div>
<Button variant="outline" onclick={reloadData}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
</svg>
<RefreshCw class="mr-2" size={16} />
Actualizar
</Button>
</div>

View File

@@ -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 @@
</p>
</div>
<Button onclick={handleCreateClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
<Plus size={16} class="mr-2" />
Nuevo Método de Valoración
</Button>
</div>
@@ -160,21 +147,7 @@
</Card.Description>
</div>
<Button variant="outline" onclick={reloadData}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
</svg>
<RefreshCw size={16} class="mr-2" />
Actualizar
</Button>
</div>