diff --git a/backend/api/v1/modules/a76/expediente_archivos/routes.py b/backend/api/v1/modules/a76/expediente_archivos/routes.py index c553cfee..5e753b8e 100644 --- a/backend/api/v1/modules/a76/expediente_archivos/routes.py +++ b/backend/api/v1/modules/a76/expediente_archivos/routes.py @@ -60,11 +60,17 @@ def list_expediente_archivos( page: int = Query(1, ge=1), page_size: int = Query(50, ge=1, le=200), search: str = Query(None), + status: str = Query(None), + rfc_consulta: str = Query(None), + e_document: str = Query(None), db: Session = Depends(get_core_db), current_user: Dict[str, Any] = Depends(get_current_user), ): tenant_id = validate_access_to_resource(db, company_id, current_user) - return ExpedienteArchivoService.list(db, company_id, tenant_id, page, page_size, search) + return ExpedienteArchivoService.list( + db, company_id, tenant_id, page, page_size, + search=search, status=status, rfc_consulta=rfc_consulta, e_document=e_document + ) @router.get("/{record_id}", response_model=ExpedienteArchivoResponseDTO) diff --git a/backend/api/v1/modules/a76/expediente_archivos/service.py b/backend/api/v1/modules/a76/expediente_archivos/service.py index a2ff0587..7af953cc 100644 --- a/backend/api/v1/modules/a76/expediente_archivos/service.py +++ b/backend/api/v1/modules/a76/expediente_archivos/service.py @@ -75,6 +75,9 @@ class ExpedienteArchivoService: page: int = 1, page_size: int = 50, search: Optional[str] = None, + status: Optional[str] = None, + rfc_consulta: Optional[str] = None, + e_document: Optional[str] = None, ) -> ExpedienteArchivoListResponse: query = ( db.query(ExpedienteArchivo) @@ -84,11 +87,22 @@ class ExpedienteArchivoService: ExpedienteArchivo.deleted_at.is_(None), ) ) + if status: + query = query.filter(ExpedienteArchivo.status == status) + if rfc_consulta: + query = query.filter(ExpedienteArchivo.rfc_consulta.ilike(f"%{rfc_consulta}%")) + if e_document: + query = query.filter(ExpedienteArchivo.e_document.ilike(f"%{e_document}%")) if search: like = f"%{search}%" query = query.filter( - ExpedienteArchivo.e_document.ilike(like) - | ExpedienteArchivo.tipo_documento.ilike(like) + or_( + ExpedienteArchivo.e_document.ilike(like), + ExpedienteArchivo.tipo_documento.ilike(like), + ExpedienteArchivo.rfc_consulta.ilike(like), + ExpedienteArchivo.num_operacion.ilike(like), + ExpedienteArchivo.nombre_archivo.ilike(like), + ) ) total = query.count() items = query.order_by(ExpedienteArchivo.id.desc()).offset((page - 1) * page_size).limit(page_size).all() diff --git a/backend/api/v1/modules/a76/general_catalogs/doda/routes.py b/backend/api/v1/modules/a76/general_catalogs/doda/routes.py index 7ed58c89..b5ed8848 100644 --- a/backend/api/v1/modules/a76/general_catalogs/doda/routes.py +++ b/backend/api/v1/modules/a76/general_catalogs/doda/routes.py @@ -82,7 +82,7 @@ async def export_doda_list( """ Listado al estilo legacy: filtra `doda_date` (YYYYMMDD) entre inicio y fin. """ - tenant_id = int(validate_access_to_resource(db, company_id, current_user)) + tenant_id = int(validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])) try: d0, d1, fmt, mode = parse_export_params( date_from, date_to, file_format, date_mode @@ -126,7 +126,7 @@ async def export_doda_pedimentos( Reporte por DODA seleccionado: columnas alineadas al listado de pedimentos (PATENTE, DOCUMENTO, COVE, etc.). Si no hay líneas, se devuelve el archivo solo con encabezados. """ - tenant_id = int(validate_access_to_resource(db, company_id, current_user)) + tenant_id = int(validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])) try: fmt = parse_pedimento_export_format(file_format) except ValueError as e: @@ -168,6 +168,11 @@ _crud_router = TenantCRUDRoutes( id_name="doda_id", enable_list=True, enable_filters=True, + list_permissions=["cat_doda.view"], + get_permissions=["cat_doda.view"], + create_permissions=["cat_doda.create"], + update_permissions=["cat_doda.edit"], + delete_permissions=["cat_doda.delete"], ).router router.include_router(_crud_router) @@ -183,7 +188,7 @@ async def get_doda_detail( db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"]) doda = DodaService.get_by_id(db, doda_id, tenant_id, company_id) if not doda: raise HTTPException( diff --git a/backend/api/v1/modules/a76/general_catalogs/electronic_notices/routes.py b/backend/api/v1/modules/a76/general_catalogs/electronic_notices/routes.py index 576f4cf0..16c7629d 100644 --- a/backend/api/v1/modules/a76/general_catalogs/electronic_notices/routes.py +++ b/backend/api/v1/modules/a76/general_catalogs/electronic_notices/routes.py @@ -28,6 +28,11 @@ router = TenantCRUDRoutes( resource_name="Electronic Notice", enable_list=True, enable_filters=True, + list_permissions=["cat_notices.view"], + get_permissions=["cat_notices.view"], + create_permissions=["cat_notices.create"], + update_permissions=["cat_notices.edit"], + delete_permissions=["cat_notices.delete"], ).router @@ -43,7 +48,7 @@ async def get_notices_by_pedimento( current_user: dict = Depends(get_current_user), ): """Get all electronic notices for a specific pedimento""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_notices.view"]) notices = ElectronicNoticeService.get_by_pedimento( db, pedimento, tenant_id, company_id) return [ElectronicNoticeResponseDTO.model_validate(notice) for notice in notices] @@ -61,7 +66,7 @@ async def get_notices_by_status( current_user: dict = Depends(get_current_user), ): """Get all electronic notices with a specific status""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_notices.view"]) notices = ElectronicNoticeService.get_by_status( db, status, tenant_id, company_id) return [ElectronicNoticeResponseDTO.model_validate(notice) for notice in notices] diff --git a/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/routes.py b/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/routes.py index 3f2107d6..03f5efd7 100644 --- a/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/routes.py +++ b/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/routes.py @@ -16,7 +16,6 @@ router = APIRouter(prefix="/code-pedimento-regimens") def list_code_pedimento_regimens( page: int = Query(1, ge=1, description="Número de página"), page_size: int = Query(50, ge=1, le=1000, description="Tamaño de página"), - company_id: int = Query(..., description="ID de la empresa"), code: str = Query(None, description="Filter by code"), regime: str = Query(None, description="Filter by regime"), type: str = Query(None, description="Filter by type"), @@ -24,8 +23,6 @@ def list_code_pedimento_regimens( db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["ref_pedimento_regimens.view"]) skip = (page - 1) * page_size query = db.query(CodePedimentoRegimen) diff --git a/backend/api/v1/modules/public/reference_data/containers/routes.py b/backend/api/v1/modules/public/reference_data/containers/routes.py index 6f0bbe4b..7182f848 100644 --- a/backend/api/v1/modules/public/reference_data/containers/routes.py +++ b/backend/api/v1/modules/public/reference_data/containers/routes.py @@ -1,7 +1,7 @@ from typing import Any, Dict from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource +from core.security import get_current_user, has_role from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from sqlalchemy import or_ @@ -16,14 +16,10 @@ router = APIRouter(prefix="/containers") async def list_containers( 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"), - company_id: int = Query(..., description="ID de la empresa"), search: str = Query(None, description="Término de búsqueda"), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - # 🛡️ Permiso de Lectura (Listado) - validate_access_to_resource(db, company_id, current_user, required_permissions=["ref_containers.view"]) - skip = (page - 1) * page_size query = db.query(Container) @@ -48,13 +44,9 @@ async def list_containers( @router.get("/{key}", response_model=ContainerDTO) async def get_container( key: str, - company_id: int = Query(..., description="ID de la empresa"), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - # 🛡️ Permiso de Lectura (Individual) - validate_access_to_resource(db, company_id, current_user, required_permissions=["ref_containers.view"]) - obj = db.query(Container).filter(Container.key == key).first() if not obj: raise HTTPException(status_code=404, detail="Not found") @@ -64,13 +56,9 @@ async def get_container( @router.post("/", response_model=ContainerDTO, status_code=201) async def create_container( data: ContainerDTO, - company_id: int = Query(..., description="ID de la empresa"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + current_user: dict = Depends(has_role("admin")), ): - # 🛡️ Permiso de Creación - validate_access_to_resource(db, company_id, current_user, required_permissions=["ref_containers.create"]) - obj = Container(**data.dict()) db.add(obj) db.commit() @@ -82,13 +70,9 @@ async def create_container( async def update_container( key: str, data: ContainerDTO, - company_id: int = Query(..., description="ID de la empresa"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + current_user: dict = Depends(has_role("admin")), ): - # 🛡️ Permiso de Edición - validate_access_to_resource(db, company_id, current_user, required_permissions=["ref_containers.edit"]) - obj = db.query(Container).filter(Container.key == key).first() if not obj: raise HTTPException(status_code=404, detail="Not found") @@ -102,16 +86,12 @@ async def update_container( @router.delete("/{key}", status_code=204) async def delete_container( key: str, - company_id: int = Query(..., description="ID de la empresa"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + current_user: dict = Depends(has_role("admin")), ): - # 🛡️ Permiso de Borrado - validate_access_to_resource(db, company_id, current_user, required_permissions=["ref_containers.delete"]) - obj = db.query(Container).filter(Container.key == key).first() if not obj: raise HTTPException(status_code=404, detail="Not found") db.delete(obj) db.commit() - return None \ No newline at end of file + return None diff --git a/backend/api/v1/modules/public/reference_data/countries/routes.py b/backend/api/v1/modules/public/reference_data/countries/routes.py index 90aea08b..45d76eea 100644 --- a/backend/api/v1/modules/public/reference_data/countries/routes.py +++ b/backend/api/v1/modules/public/reference_data/countries/routes.py @@ -1,7 +1,7 @@ from typing import Any, Dict from core.database import get_core_db -from core.security import get_current_user +from core.security import get_current_user, has_role from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session @@ -15,14 +15,10 @@ router = APIRouter(prefix="/countries") async def list_countries( 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"), - company_id: int = Query(..., description="ID de la empresa"), search: str = Query(None, description="Término de búsqueda"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["ref_countries.view", "cat_countries.view"], require_all=False) - + """Endpoint público para obtener lista de países - no requiere autenticación""" skip = (page - 1) * page_size query = db.query(Country) @@ -53,8 +49,6 @@ async def get_country( db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource as validate_perm - validate_perm(current_user, "cat_countries", "view") obj = db.query(Country).filter(Country.m3_key == m3_key).first() if not obj: raise HTTPException(status_code=404, detail="Not found") @@ -65,10 +59,8 @@ async def get_country( async def create_country( data: CountryDTO, db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + current_user: dict = Depends(has_role("admin")), ): - from core.security import validate_access_to_resource as validate_perm - validate_perm(current_user, "cat_countries", "create") obj = Country(**data.dict()) db.add(obj) db.commit() @@ -81,10 +73,8 @@ async def update_country( m3_key: str, data: CountryDTO, db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + current_user: dict = Depends(has_role("admin")), ): - from core.security import validate_access_to_resource as validate_perm - validate_perm(current_user, "cat_countries", "edit") obj = db.query(Country).filter(Country.m3_key == m3_key).first() if not obj: raise HTTPException(status_code=404, detail="Not found") @@ -99,10 +89,8 @@ async def update_country( async def delete_country( m3_key: str, db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + current_user: dict = Depends(has_role("admin")), ): - from core.security import validate_access_to_resource as validate_perm - validate_perm(current_user, "cat_countries", "delete") obj = db.query(Country).filter(Country.m3_key == m3_key).first() if not obj: raise HTTPException(status_code=404, detail="Not found") diff --git a/backend/api/v1/modules/public/reference_data/currency_types/routes.py b/backend/api/v1/modules/public/reference_data/currency_types/routes.py index fab4e557..6d860ba3 100644 --- a/backend/api/v1/modules/public/reference_data/currency_types/routes.py +++ b/backend/api/v1/modules/public/reference_data/currency_types/routes.py @@ -1,7 +1,7 @@ from typing import Any, Dict from core.database import get_core_db -from core.security import get_current_user +from core.security import get_current_user, has_role from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from sqlalchemy import or_ @@ -16,13 +16,10 @@ router = APIRouter(prefix="/currency-types") async def list_currency_types( 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"), - company_id: int = Query(..., description="ID de la empresa"), search: str = Query(None, description="Término de búsqueda"), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["ref_currency_types.view", "cat_currency.view"], require_all=False) skip = (page - 1) * page_size query = db.query(CurrencyType) @@ -51,8 +48,6 @@ async def get_currency_type( db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource as validate_perm - validate_perm(current_user, "cat_currency_types", "view") obj = db.query(CurrencyType).filter(CurrencyType.code == code).first() if not obj: raise HTTPException(status_code=404, detail="Not found") @@ -63,10 +58,8 @@ async def get_currency_type( async def create_currency_type( data: CurrencyTypeDTO, db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + current_user: dict = Depends(has_role("admin")), ): - from core.security import validate_access_to_resource as validate_perm - validate_perm(current_user, "cat_currency_types", "create") obj = CurrencyType(**data.dict()) db.add(obj) db.commit() @@ -79,10 +72,8 @@ async def update_currency_type( code: str, data: CurrencyTypeDTO, db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + current_user: dict = Depends(has_role("admin")), ): - from core.security import validate_access_to_resource as validate_perm - validate_perm(current_user, "cat_currency_types", "edit") obj = db.query(CurrencyType).filter(CurrencyType.code == code).first() if not obj: raise HTTPException(status_code=404, detail="Not found") @@ -97,10 +88,8 @@ async def update_currency_type( async def delete_currency_type( code: str, db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + current_user: dict = Depends(has_role("admin")), ): - from core.security import validate_access_to_resource as validate_perm - validate_perm(current_user, "cat_currency_types", "delete") obj = db.query(CurrencyType).filter(CurrencyType.code == code).first() if not obj: raise HTTPException(status_code=404, detail="Not found") diff --git a/backend/api/v1/modules/public/reference_data/customs_sections/routes.py b/backend/api/v1/modules/public/reference_data/customs_sections/routes.py index 6a5b51df..767462d3 100644 --- a/backend/api/v1/modules/public/reference_data/customs_sections/routes.py +++ b/backend/api/v1/modules/public/reference_data/customs_sections/routes.py @@ -16,13 +16,10 @@ router = APIRouter(prefix="/customs-sections") def list_customs_sections( 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"), - company_id: int = Query(..., description="ID de la empresa"), search: str = Query(None, description="Término de búsqueda"), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["ref_customs_sections.view"]) skip = (page - 1) * page_size query = db.query(CustomsSection) @@ -47,12 +44,9 @@ def list_customs_sections( @router.get("/{customs_code}", response_model=CustomsSectionDTO) def get_customs_section( customs_code: str, - company_id: int = Query(..., description="ID de la empresa"), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["ref_customs_sections.view"]) obj = ( db.query(CustomsSection) .filter(CustomsSection.customs_code == customs_code) @@ -66,12 +60,9 @@ def get_customs_section( @router.post("/", response_model=CustomsSectionDTO, status_code=201) def create_customs_section( data: CustomsSectionDTO, - company_id: int = Query(..., description="ID de la empresa"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + current_user: dict = Depends(has_role("admin")), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["ref_customs_sections.edit"]) obj = CustomsSection(**data.dict()) db.add(obj) db.commit() @@ -83,12 +74,9 @@ def create_customs_section( def update_customs_section( customs_code: str, data: CustomsSectionDTO, - company_id: int = Query(..., description="ID de la empresa"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + current_user: dict = Depends(has_role("admin")), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["ref_customs_sections.edit"]) obj = ( db.query(CustomsSection) .filter(CustomsSection.customs_code == customs_code) @@ -106,12 +94,9 @@ def update_customs_section( @router.delete("/{customs_code}", status_code=204) def delete_customs_section( customs_code: str, - company_id: int = Query(..., description="ID de la empresa"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + current_user: dict = Depends(has_role("admin")), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["ref_customs_sections.edit"]) obj = ( db.query(CustomsSection) .filter(CustomsSection.customs_code == customs_code) diff --git a/backend/api/v1/modules/public/reference_data/customs_warehouses/routes.py b/backend/api/v1/modules/public/reference_data/customs_warehouses/routes.py index b32a4815..03947cd0 100644 --- a/backend/api/v1/modules/public/reference_data/customs_warehouses/routes.py +++ b/backend/api/v1/modules/public/reference_data/customs_warehouses/routes.py @@ -16,13 +16,10 @@ router = APIRouter(prefix="/customs-warehouses") def list_customs_warehouses( 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"), - company_id: int = Query(..., description="ID de la empresa"), search: str = Query(None, description="Término de búsqueda"), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["ref_customs_warehouses.view", "cat_warehouses.view"], require_all=False) skip = (page - 1) * page_size query = db.query(CustomsWarehouse) diff --git a/backend/api/v1/modules/public/reference_data/identifiers/routes.py b/backend/api/v1/modules/public/reference_data/identifiers/routes.py index 9f28495f..b2263881 100644 --- a/backend/api/v1/modules/public/reference_data/identifiers/routes.py +++ b/backend/api/v1/modules/public/reference_data/identifiers/routes.py @@ -1,7 +1,7 @@ from typing import Any, Dict from core.database import get_core_db -from core.security import get_current_user +from core.security import get_current_user, has_role from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy import or_ from sqlalchemy.orm import Session @@ -22,8 +22,6 @@ async def list_identifiers( db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource as validate_perm - validate_perm(current_user, "cat_identifiers", "view") skip = (page - 1) * page_size query = db.query(IdentifierCatalog) @@ -67,8 +65,6 @@ async def get_identifier( db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource as validate_perm - validate_perm(current_user, "cat_identifiers", "view") obj = db.query(IdentifierCatalog).filter(IdentifierCatalog.key == key).first() if not obj: raise HTTPException(status_code=404, detail="Not found") @@ -79,10 +75,8 @@ async def get_identifier( async def create_identifier( data: IdentifierDTO, db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + current_user: dict = Depends(has_role("admin")), ): - from core.security import validate_access_to_resource as validate_perm - validate_perm(current_user, "cat_identifiers", "create") # Check if already exists existing = db.query(IdentifierCatalog).filter(IdentifierCatalog.key == data.key).first() if existing: @@ -100,10 +94,8 @@ async def update_identifier( key: str, data: IdentifierDTO, db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + current_user: dict = Depends(has_role("admin")), ): - from core.security import validate_access_to_resource as validate_perm - validate_perm(current_user, "cat_identifiers", "edit") obj = db.query(IdentifierCatalog).filter(IdentifierCatalog.key == key).first() if not obj: raise HTTPException(status_code=404, detail="Not found") @@ -118,10 +110,8 @@ async def update_identifier( async def delete_identifier( key: str, db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + current_user: dict = Depends(has_role("admin")), ): - from core.security import validate_access_to_resource as validate_perm - validate_perm(current_user, "cat_identifiers", "delete") obj = db.query(IdentifierCatalog).filter(IdentifierCatalog.key == key).first() if not obj: raise HTTPException(status_code=404, detail="Not found") diff --git a/backend/api/v1/modules/public/reference_data/incoterms/routes.py b/backend/api/v1/modules/public/reference_data/incoterms/routes.py index 7fac2546..6661e334 100644 --- a/backend/api/v1/modules/public/reference_data/incoterms/routes.py +++ b/backend/api/v1/modules/public/reference_data/incoterms/routes.py @@ -16,15 +16,12 @@ router = APIRouter(prefix="/incoterms") async def list_incoterms( 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"), - company_id: int = Query(..., description="ID de la empresa"), code: str = Query(None, description="Filtrar por clave"), description: str = Query(None, description="Filtrar por descripción"), search: str = Query(None, description="Término de búsqueda"), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["ref_incoterms.view", "cat_incoterms.view"], require_all=False) skip = (page - 1) * page_size query = db.query(Incoterm) diff --git a/backend/api/v1/modules/public/reference_data/invoice_types/routes.py b/backend/api/v1/modules/public/reference_data/invoice_types/routes.py index 638c2917..c2b34297 100644 --- a/backend/api/v1/modules/public/reference_data/invoice_types/routes.py +++ b/backend/api/v1/modules/public/reference_data/invoice_types/routes.py @@ -16,15 +16,11 @@ router = APIRouter(prefix="/invoice-types") def list_invoice_types( page: int = Query(1, ge=1), page_size: int = Query(50, ge=1, le=100), - company_id: int = Query(..., description="ID de la empresa"), type: Optional[str] = Query(None, description="Filter by type"), operation: Optional[str] = Query(None, description="Filter by operation type (imp, exp, both)"), search: str = Query(None, description="Término de búsqueda"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["ref_invoice_types.view", "cat_inv_types.view"], require_all=False) query = db.query(InvoiceType) if search: diff --git a/backend/api/v1/modules/public/reference_data/material_types/routes.py b/backend/api/v1/modules/public/reference_data/material_types/routes.py index 49101606..b3982a93 100644 --- a/backend/api/v1/modules/public/reference_data/material_types/routes.py +++ b/backend/api/v1/modules/public/reference_data/material_types/routes.py @@ -16,14 +16,11 @@ router = APIRouter(prefix="/material-types") async def list_material_types( page: int = Query(1, ge=1, description="Número de página"), page_size: int = Query(50, ge=1, le=1000, description="Tamaño de página"), - company_id: int = Query(..., description="ID de la empresa"), type: str = Query(None, description="Filtrar por tipo (ACTIVO FIJO, MATERIALES, PRODUCTOS)"), search: str = Query(None, description="Término de búsqueda"), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["ref_material_types.view", "cat_material_types.view"], require_all=False) skip = (page - 1) * page_size query = db.query(MaterialType) diff --git a/backend/api/v1/modules/public/reference_data/payment_methods/routes.py b/backend/api/v1/modules/public/reference_data/payment_methods/routes.py index cf0d3b34..3e728f4b 100644 --- a/backend/api/v1/modules/public/reference_data/payment_methods/routes.py +++ b/backend/api/v1/modules/public/reference_data/payment_methods/routes.py @@ -16,13 +16,10 @@ router = APIRouter(prefix="/payment-methods") def list_payment_methods( 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"), - company_id: int = Query(..., description="ID de la empresa"), search: str = Query(None, description="Término de búsqueda"), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["pedimentos_payments.view"]) skip = (page - 1) * page_size query = db.query(PaymentMethod) @@ -47,12 +44,9 @@ def list_payment_methods( @router.get("/{key}", response_model=PaymentMethodDTO) def get_payment_method( key: str, - company_id: int = Query(..., description="ID de la empresa"), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["pedimentos_payments.view"]) obj = db.query(PaymentMethod).filter(PaymentMethod.key == key).first() if not obj: raise HTTPException(status_code=404, detail="Not found") diff --git a/backend/api/v1/modules/public/reference_data/pedimento_codes/routes.py b/backend/api/v1/modules/public/reference_data/pedimento_codes/routes.py index b25cdc48..0d7aa39e 100644 --- a/backend/api/v1/modules/public/reference_data/pedimento_codes/routes.py +++ b/backend/api/v1/modules/public/reference_data/pedimento_codes/routes.py @@ -16,13 +16,10 @@ router = APIRouter(prefix="/pedimento-codes") def list_pedimento_codes( page: int = Query(1, ge=1, description="Número de página"), page_size: int = Query(50, ge=1, le=1000, description="Tamaño de página"), - company_id: int = Query(..., description="ID de la empresa"), search: str = Query(None, description="Término de búsqueda"), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["ref_pedimento_codes.view"]) skip = (page - 1) * page_size query = db.query(PedimentoCode) @@ -47,12 +44,9 @@ def list_pedimento_codes( @router.get("/{code}", response_model=PedimentoCodeDTO) def get_pedimento_code( code: str, - company_id: int = Query(..., description="ID de la empresa"), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["ref_pedimento_codes .view"]) obj = db.query(PedimentoCode).filter(PedimentoCode.code == code).first() if not obj: raise HTTPException(status_code=404, detail="Not found") diff --git a/backend/api/v1/modules/public/reference_data/pedimento_regimens/routes.py b/backend/api/v1/modules/public/reference_data/pedimento_regimens/routes.py index 6cfbae6f..678fa194 100644 --- a/backend/api/v1/modules/public/reference_data/pedimento_regimens/routes.py +++ b/backend/api/v1/modules/public/reference_data/pedimento_regimens/routes.py @@ -1,7 +1,7 @@ from typing import Any, Dict from core.database import get_core_db -from core.security import get_current_user +from core.security import get_current_user, has_role from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from sqlalchemy import or_ @@ -16,13 +16,10 @@ router = APIRouter(prefix="/pedimento-regimens") def list_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"), - company_id: int = Query(..., description="ID de la empresa"), search: str = Query(None, description="Término de búsqueda"), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["ref_pedimento_regimens.view"]) skip = (page - 1) * page_size query = db.query(RegimenPedimento) @@ -47,12 +44,9 @@ def list_pedimento_regimens( @router.get("/{key}", response_model=RegimenPedimentoDTO) def get_pedimento_regimen( key: str, - company_id: int = Query(..., description="ID de la empresa"), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["ref_pedimento_regimens.view"]) obj = db.query(RegimenPedimento).filter(RegimenPedimento.code == key).first() if not obj: raise HTTPException(status_code=404, detail="Not found") @@ -62,13 +56,9 @@ def get_pedimento_regimen( @router.post("/", response_model=RegimenPedimentoDTO, status_code=201) def create_pedimento_regimen( data: RegimenPedimentoDTO, - company_id: int = Query(..., description="ID de la empresa"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + current_user: dict = Depends(has_role("admin")), ): - from core.security import validate_access_to_resource - # Mutation for reference data usually restricted to admin role or specific perm - validate_access_to_resource(db, company_id, current_user, ["ref_pedimento_regimens.edit"]) obj = RegimenPedimento(**data.model_dump()) db.add(obj) db.commit() @@ -80,12 +70,9 @@ def create_pedimento_regimen( def update_pedimento_regimen( key: str, data: RegimenPedimentoDTO, - company_id: int = Query(..., description="ID de la empresa"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + current_user: dict = Depends(has_role("admin")), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["ref_pedimento_regimens.edit"]) obj = db.query(RegimenPedimento).filter(RegimenPedimento.code == key).first() if not obj: raise HTTPException(status_code=404, detail="Not found") @@ -99,12 +86,9 @@ def update_pedimento_regimen( @router.delete("/{key}", status_code=204) def delete_pedimento_regimen( key: str, - company_id: int = Query(..., description="ID de la empresa"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + current_user: dict = Depends(has_role("admin")), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["ref_pedimento_regimens.edit"]) obj = db.query(RegimenPedimento).filter(RegimenPedimento.code == key).first() if not obj: raise HTTPException(status_code=404, detail="Not found") diff --git a/backend/api/v1/modules/public/reference_data/pedimento_transport_catalog/routes.py b/backend/api/v1/modules/public/reference_data/pedimento_transport_catalog/routes.py index 63154da8..e3a0db27 100644 --- a/backend/api/v1/modules/public/reference_data/pedimento_transport_catalog/routes.py +++ b/backend/api/v1/modules/public/reference_data/pedimento_transport_catalog/routes.py @@ -14,15 +14,11 @@ router = APIRouter(prefix="/pedimento-transport-catalog") @router.get("/", response_model=Dict[str, Any]) async def list_pedimento_transport_catalog( - company_id: int = Query(..., description="ID de la empresa"), page: int = Query(1, ge=1, description="Numero de pagina"), page_size: int = Query(100, ge=1, le=200, description="Tamano de pagina"), search: str = Query(None, description="Término de búsqueda"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["pedimentos_anexo22.view"]) skip = (page - 1) * page_size query = db.query(PedimentoTransportCatalog) @@ -47,14 +43,7 @@ async def list_pedimento_transport_catalog( @router.get("/{code}", response_model=PedimentoTransportCatalogDTO) -async def get_pedimento_transport_catalog( - code: str, - company_id: int = Query(..., description="ID de la empresa"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["pedimentos_anexo22.view"]) +async def get_pedimento_transport_catalog(code: str, db: Session = Depends(get_core_db)): obj = ( db.query(PedimentoTransportCatalog) .filter(PedimentoTransportCatalog.code == code) @@ -68,12 +57,9 @@ async def get_pedimento_transport_catalog( @router.post("/", response_model=PedimentoTransportCatalogDTO, status_code=201) async def create_pedimento_transport_catalog( data: PedimentoTransportCatalogDTO, - company_id: int = Query(..., description="ID de la empresa"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + user=Depends(get_current_user), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["pedimentos_anexo22.view"]) obj = PedimentoTransportCatalog(**data.model_dump()) db.add(obj) db.commit() @@ -85,12 +71,9 @@ async def create_pedimento_transport_catalog( async def update_pedimento_transport_catalog( code: str, data: PedimentoTransportCatalogDTO, - company_id: int = Query(..., description="ID de la empresa"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + user=Depends(get_current_user), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["pedimentos_anexo22.view"]) obj = ( db.query(PedimentoTransportCatalog) .filter(PedimentoTransportCatalog.code == code) @@ -108,12 +91,9 @@ async def update_pedimento_transport_catalog( @router.delete("/{code}", status_code=204) async def delete_pedimento_transport_catalog( code: str, - company_id: int = Query(..., description="ID de la empresa"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + user=Depends(get_current_user), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["pedimentos_anexo22.view"]) obj = ( db.query(PedimentoTransportCatalog) .filter(PedimentoTransportCatalog.code == code) diff --git a/backend/api/v1/modules/public/reference_data/states/routes.py b/backend/api/v1/modules/public/reference_data/states/routes.py index f88f614d..52d09f53 100644 --- a/backend/api/v1/modules/public/reference_data/states/routes.py +++ b/backend/api/v1/modules/public/reference_data/states/routes.py @@ -16,13 +16,10 @@ router = APIRouter(prefix="/states") async def list_states( 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"), - company_id: int = Query(..., description="ID de la empresa"), search: str = Query(None, description="Término de búsqueda"), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["ref_states.view"]) skip = (page - 1) * page_size query = db.query(State) diff --git a/backend/api/v1/modules/public/reference_data/transport_modes/routes.py b/backend/api/v1/modules/public/reference_data/transport_modes/routes.py index 3a134db7..cfe4405d 100644 --- a/backend/api/v1/modules/public/reference_data/transport_modes/routes.py +++ b/backend/api/v1/modules/public/reference_data/transport_modes/routes.py @@ -1,7 +1,7 @@ from typing import Any, Dict from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource +from core.security import get_current_user from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from sqlalchemy import or_ @@ -16,14 +16,9 @@ router = APIRouter(prefix="/transport-modes") async def list_transport_modes( 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"), - company_id: int = Query(..., description="ID de la empresa"), search: str = Query(None, description="Término de búsqueda"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), ): - # 🛡️ Permiso de Lectura (Listado) - validate_access_to_resource(db, company_id, current_user, required_permissions=["ref_transport_modes.view"]) - skip = (page - 1) * page_size query = db.query(TransportMode) @@ -46,15 +41,7 @@ async def list_transport_modes( @router.get("/{key}", response_model=TransportModeDTO) -async def get_transport_mode( - key: str, - company_id: int = Query(..., description="ID de la empresa"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - # 🛡️ Permiso de Lectura (Individual) - validate_access_to_resource(db, company_id, current_user, required_permissions=["ref_transport_modes.view"]) - +async def get_transport_mode(key: str, db: Session = Depends(get_core_db)): obj = db.query(TransportMode).filter(TransportMode.key == key).first() if not obj: raise HTTPException(status_code=404, detail="Not found") @@ -64,13 +51,9 @@ async def get_transport_mode( @router.post("/", response_model=TransportModeDTO, status_code=201) async def create_transport_mode( data: TransportModeDTO, - company_id: int = Query(..., description="ID de la empresa"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + user=Depends(get_current_user), ): - # 🛡️ Permiso de Creación - validate_access_to_resource(db, company_id, current_user, required_permissions=["ref_transport_modes.create"]) - obj = TransportMode(**data.dict()) db.add(obj) db.commit() @@ -82,13 +65,9 @@ async def create_transport_mode( async def update_transport_mode( key: str, data: TransportModeDTO, - company_id: int = Query(..., description="ID de la empresa"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + user=Depends(get_current_user), ): - # 🛡️ Permiso de Edición - validate_access_to_resource(db, company_id, current_user, required_permissions=["ref_transport_modes.edit"]) - obj = db.query(TransportMode).filter(TransportMode.key == key).first() if not obj: raise HTTPException(status_code=404, detail="Not found") @@ -101,17 +80,11 @@ async def update_transport_mode( @router.delete("/{key}", status_code=204) async def delete_transport_mode( - key: str, - company_id: int = Query(..., description="ID de la empresa"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), + key: str, db: Session = Depends(get_core_db), user=Depends(get_current_user) ): - # 🛡️ Permiso de Borrado - validate_access_to_resource(db, company_id, current_user, required_permissions=["ref_transport_modes.delete"]) - obj = db.query(TransportMode).filter(TransportMode.key == key).first() if not obj: raise HTTPException(status_code=404, detail="Not found") db.delete(obj) db.commit() - return None \ No newline at end of file + return None diff --git a/backend/api/v1/modules/public/reference_data/transport_types/routes.py b/backend/api/v1/modules/public/reference_data/transport_types/routes.py index d7736b53..563d5e0b 100644 --- a/backend/api/v1/modules/public/reference_data/transport_types/routes.py +++ b/backend/api/v1/modules/public/reference_data/transport_types/routes.py @@ -16,13 +16,9 @@ router = APIRouter(prefix="/transport-types") def list_transport_types( 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"), - company_id: int = Query(..., description="ID de la empresa"), search: str = Query(None, description="Término de búsqueda"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["ref_transport_types.view"]) skip = (page - 1) * page_size query = db.query(TransportType) diff --git a/backend/api/v1/modules/public/reference_data/valuation_methods/routes.py b/backend/api/v1/modules/public/reference_data/valuation_methods/routes.py index a0c67c7a..e0f7c65d 100644 --- a/backend/api/v1/modules/public/reference_data/valuation_methods/routes.py +++ b/backend/api/v1/modules/public/reference_data/valuation_methods/routes.py @@ -16,13 +16,10 @@ router = APIRouter(prefix="/valuation-methods") async def list_valuation_methods( 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"), - company_id: int = Query(..., description="ID de la empresa"), search: str = Query(None, description="Término de búsqueda"), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["ref_valuation_methods.view", "cat_valuation.view"], require_all=False) skip = (page - 1) * page_size query = db.query(ValuationMethod) diff --git a/frontend/src/lib/api/dashboard/a76/expediente-archivos.ts b/frontend/src/lib/api/dashboard/a76/expediente-archivos.ts index b706366b..02d8ec82 100644 --- a/frontend/src/lib/api/dashboard/a76/expediente-archivos.ts +++ b/frontend/src/lib/api/dashboard/a76/expediente-archivos.ts @@ -106,7 +106,14 @@ class ExpedienteArchivosApi { async list( companyId: string | number, - params?: Record + params?: { + page?: number; + page_size?: number; + search?: string; + status?: string; + rfc_consulta?: string; + e_document?: string; + } ): Promise> { const queryParams = new URLSearchParams({ company_id: companyId.toString() }); if (params) { diff --git a/frontend/src/lib/components/dashboard/digitalizacion/columns.ts b/frontend/src/lib/components/dashboard/digitalizacion/columns.ts index f31b4a46..b7e2b43d 100644 --- a/frontend/src/lib/components/dashboard/digitalizacion/columns.ts +++ b/frontend/src/lib/components/dashboard/digitalizacion/columns.ts @@ -107,6 +107,16 @@ export function createColumns( header: 'Tipo Documento', cell: ({ row }) => row.original.tipo_documento || '-' }, + { + accessorKey: 'rfc_consulta', + header: 'RFC Consulta', + cell: ({ row }) => row.original.rfc_consulta || '-' + }, + { + accessorKey: 'nombre_archivo', + header: 'Archivo', + cell: ({ row }) => row.original.nombre_archivo || '-' + }, { accessorKey: 'e_document', header: 'E-Document', diff --git a/frontend/src/lib/components/dashboard/digitalizacion/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/digitalizacion/create-edit-dialog.svelte index 26451d7e..188ca3d5 100644 --- a/frontend/src/lib/components/dashboard/digitalizacion/create-edit-dialog.svelte +++ b/frontend/src/lib/components/dashboard/digitalizacion/create-edit-dialog.svelte @@ -320,6 +320,18 @@ /> +
+ + (formData.rfc_consulta = (e.target as HTMLInputElement).value.toUpperCase())} + placeholder="RFC para consulta" + maxlength={13} + disabled={loading} + /> +
+
($page.url.searchParams.get('type') || 'both'); - let filterDebounce: ReturnType | null = null; // Estado para el diálogo de crear let showCreateDialog = $state(false); let error = $state(data.error || null); - // Permisos - const canView = $derived(userHasPermission($authStore.user, 'partners_mgmt.view')); - const canCreate = $derived(userHasPermission($authStore.user, 'partners_mgmt.create')); - const canEdit = $derived(userHasPermission($authStore.user, 'partners_mgmt.edit')); - const canDelete = $derived(userHasPermission($authStore.user, 'partners_mgmt.delete')); - // --- Lifecycle --- onMount(() => { if (browser) { - const getCookie = (name: string): string | null => { + // Sincronizar token de cookies a localStorage si es necesario + const getCookie = (name: string) => { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) return parts.pop()?.split(';').shift() || null; return null; }; - const cookieToken = getCookie('access_token'); const localToken = localStorage.getItem('access_token'); - if (cookieToken && cookieToken !== localToken) localStorage.setItem('access_token', cookieToken); @@ -77,7 +64,7 @@ // --- Actions --- - async function loadItems(pageToLoad = 1, append = false) { + async function loadItems(pageToLoad = 1) { const companyId = companyStore.activeCompany?.id; if (!companyId) return; @@ -85,10 +72,13 @@ try { const filters: any = {}; if (searchType !== 'both') filters.type = searchType; - const trimmedName = searchName.trim(); - const trimmedRfc = searchRfc.trim(); - if (trimmedName) filters.name = trimmedName; - if (trimmedRfc) filters.rfc = trimmedRfc; + // Note: The API technically supports name/rfc fitlering if backend implements it. + // Assuming backend supports 'name' and 'rfc' query params based on standard patterns, + // or we filter client side if the list is small. + // Given pagination, we should try sending them. If backend ignores them, we might need client filtering. + // Ideally backend should handle this. I will assume backend filters for now or add query params. + if (searchName) filters.name = searchName; + if (searchRfc) filters.rfc = searchRfc; const response = await clientsProvidersApi.list(companyId, pageToLoad, pageSize, filters); @@ -103,11 +93,7 @@ } if (response.data) { - if (append) { - items = [...items, ...response.data.items]; - } else { - items = response.data.items; - } + items = response.data.items; totalItems = response.data.total; currentPage = response.data.page; } @@ -119,13 +105,13 @@ } } - async function loadMore() { - if (isLoading || !hasMore) return; - await loadItems(currentPage + 1, true); - } - function handleTypeChange(value: string) { searchType = value; + loadItems(1); + } + + function handleSearch() { + loadItems(1); } function selectItem(item: ClientProvider) { @@ -142,10 +128,6 @@ if (selectedItem) goto(`/dashboard/clients_and_providers/edit/${selectedItem.id}`); } - function handleSearch() { - loadItems(1); - } - import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosListaSocios } from '$lib/config/shortcuts/dashboard/clients_and_providers/list'; @@ -173,218 +155,331 @@ recargar: () => loadItems(1) }) ); - - $effect(() => { - const companyId = companyStore.activeCompany?.id; - if (!browser || !companyId) return; - - searchName; - searchRfc; - searchType; - pageSize; - - if (filterDebounce) clearTimeout(filterDebounce); - filterDebounce = setTimeout(() => { - selectedItem = null; - loadItems(1); - }, 350); - - return () => { - if (filterDebounce) clearTimeout(filterDebounce); - }; - }); -
- {#if !canView} - window.location.reload()} - /> - {:else} -
-
-

Socio Comercial

-

Administración de clientes y proveedores

-
-
- - {#if canCreate} - - {/if} - {#if canEdit} - - {/if} - {#if canDelete} - - {/if} -
-
+
+ +
+

CLIENTES Y PROVEEDORES

+

+ Gestiona el catálogo de clientes y proveedores de tu empresa +

+
- {#if error} -
- {typeof error === 'string' ? error : error.detail} -
- {/if} + + {#if error} + + + Error + + {typeof error === 'string' ? error : error.detail} + + + + {/if} -
- -
- -
-
-
-

Filtros

- Busque por nombre, RFC/TAX-ID o tipo -
-
-
- - e.key === 'Enter' && handleSearch()} - /> -
-
- - e.key === 'Enter' && handleSearch()} - /> -
-
- - - - {searchType === 'both' - ? 'Todos' - : searchType === 'client' - ? 'Clientes' - : 'Proveedores'} - - - Todos - Clientes - Proveedores - - -
-
- -
-
+
+ +
+ +
+
+
+

Filtros

+ Busque por nombre, RFC/TAX-ID o tipo
-
- - -
-
-

Listado

-
- - {totalItems} registros - -
- -
- loadItems(1))} - loading={isLoading} - {hasMore} - {loadMore} - onRowClick={(row) => selectItem(row as ClientProvider)} - selectedId={selectedItem?.id} - /> -
- -
-
-

- Detalles del Registro -

-

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

-
- {taxIdOrRfcLabel(selectedItem)}: - {selectedItem?.rfc || ''} + +
+
+

Listado

+
+ + {totalItems} registros + +
-
- {#if selectedItem} -
-
- -

{selectedItem.client_or_provider}

-
- - {#if selectedItem.address} -
- -
-

{selectedItem.address.streets || ''} {selectedItem.address.exterior_number || ''}

-

{selectedItem.address.neighborhood || ''}

-

{selectedItem.address.city || ''}, {selectedItem.address.state || ''}

-

{selectedItem.address.postal_code || ''}, {selectedItem.address.country || ''}

-
-
+
+ + + + + + + + + + + + {#if isLoading} + + {:else if items.length === 0} + + {:else} + {#each items as item (item.id)} + selectItem(item)} + > + + + + + + + {/each} {/if} - - {:else} -
- -

Selecciona un registro

-
- {/if} + +
#RFC / TAX-IDNombreTipoEstatus
Cargando...
No se encontraron registros
{item.id}{item.rfc}{item.name} + {#if item.client_or_provider === 'client'} + Cliente + {:else if item.client_or_provider === 'provider'} + Proveedor + {:else} + Ambos + {/if} + + + {item.is_active ? 'Activo' : 'Inactivo'} + +
+
+ +
+ + + Página {currentPage} de {Math.ceil(totalItems / pageSize)} + +
- {/if} + + +
+
+

+ Detalles del Registro +

+

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

+
+ {taxIdOrRfcLabel(selectedItem)}: + {selectedItem?.rfc || ''} +
+
+ +
+ {#if selectedItem} +
+
+ +

{selectedItem.client_or_provider}

+
+ + {#if selectedItem.address} +
+ +
+

+ {selectedItem.address.streets || ''} + {selectedItem.address.exterior_number || ''} + {selectedItem.address.interior_number + ? 'Int ' + selectedItem.address.interior_number + : ''} +

+

{selectedItem.address.neighborhood || ''}

+

{selectedItem.address.city || ''}, {selectedItem.address.state || ''}

+

+ {selectedItem.address.postal_code || ''}, {selectedItem.address.country || ''} +

+
+
+ +
+ + {#if selectedItem.address.email} +
+ + {selectedItem.address.email} +
+ {/if} + {#if selectedItem.address.phone} +
+ + {selectedItem.address.phone} +
+ {/if} +
+ {:else} +
+

Sin dirección registrada

+
+ {/if} + + {#if selectedItem.programs} +
+ +
+
+ Programa + {selectedItem.programs.program || '-'} +
+
+ Número + {selectedItem.programs.program_number || '-'} +
+
+
+ {/if} +
+ {:else} +
+ +

Selecciona un registro

+
+ {/if} +
+
+
+
+ + +
+
+
+ + + +
+
diff --git a/frontend/src/routes/dashboard/clients_and_providers/edit/[[id]]/+page.svelte b/frontend/src/routes/dashboard/clients_and_providers/edit/[[id]]/+page.svelte index 961424b1..94d020b3 100644 --- a/frontend/src/routes/dashboard/clients_and_providers/edit/[[id]]/+page.svelte +++ b/frontend/src/routes/dashboard/clients_and_providers/edit/[[id]]/+page.svelte @@ -35,7 +35,6 @@ Briefcase } from 'lucide-svelte'; import { toast } from 'svelte-sonner'; - import ErrorState from '$lib/components/dashboard/common/error-state.svelte'; // Componentes Compartidos (Modales) import CountrySelectorDialog from '$lib/components/dashboard/goods/modales/country-selector-dialog.svelte'; @@ -49,9 +48,6 @@ // API & Stores import { clientsProvidersApi } from '$lib/api/dashboard/a76/clients-providers'; import { companyStore } from '$lib/stores/company.svelte'; - import { authStore, userHasPermission } from '$lib/auth'; - import { browser } from '$app/environment'; - // --- CONFIGURACIÓN --- let id = $derived($page.params.id); @@ -133,20 +129,6 @@ else if (!id || id === 'new') formData = getEmptyForm(); }); - // Permisos - const requiredPermission = $derived(isEditing ? 'partners_mgmt.edit' : 'partners_mgmt.create'); - const canAccess = $derived(userHasPermission($authStore.user, requiredPermission)); - - onMount(() => { - if (browser) { - const handleCompanyChange = () => { - if (isEditing) loadData(Number(id)); - }; - window.addEventListener('companyChanged', handleCompanyChange); - return () => window.removeEventListener('companyChanged', handleCompanyChange); - } - }); - async function loadData(clientId: number) { if (!companyStore.activeCompany?.id) return; loading = true; @@ -316,521 +298,530 @@ ); -
- {#if !canAccess} - window.location.reload()} - onBack={() => goto('/dashboard/clients_and_providers')} - /> - {:else} -
- -
-
-
- -

- {isEditing ? `Socio Comercial #${id}` : 'Nuevo Socio Comercial'} -

- {#if isEditing} - - {formData.is_active ? 'Activo' : 'Inactivo'} - - {:else} - Nuevo - {/if} -
-

- {isEditing - ? 'Edita la información del cliente o proveedor' - : 'Registra un nuevo cliente o proveedor en el sistema'} -

-
-
- - - - -
- -
{ - e.preventDefault(); - handleSubmit(); - }} - > - - - - - - Información General - Datos principales de identificación y clasificación. - - -
-
- - -
-
- - (formData.client_or_provider = v)} - > - - {typeLabels[formData.client_or_provider] || 'Selecciona un tipo'} - - - Cliente - Proveedor - Ambos - - -
-
- -
-
- - -
-
- - -
-
- -
-
- - -
-
- - (formData.type_nat_foreign = v)} - > - - {formData.type_nat_foreign === 'N' - ? 'Nacional' - : formData.type_nat_foreign === 'E' - ? 'Extranjero' - : 'Seleccione'} - - - Nacional - Extranjero - - -
-
- -
-
- - -
-
- - -
-
-
-
-
- - - - - - Dirección y Contacto - Ubicación fiscal y datos de contacto. - - -
-
- - -
-
-
- - -
-
- - -
-
-
- -
-
- - -
-
- - -
-
- - -
-
- -
-
- - -
-
- -
- - -
-
-
- -
- - -
-
-
- - - -
-
- - -
-
- - -
-
-
-
-
- - - - - - Programas y Certificaciones - Información sobre IMMEX, PROSEC y otras certificaciones. - - - -
-
- - Programas de Fomento -
- -
-
- - (formData.program = v)} - disabled={loading} - > - - {formData.program || 'Selecciona un programa'} - - - {#each scaiiPrograms as prog} - - {prog.label} - - {/each} - - -
-
- - -
-
- - -
-
- -
- - -
-
-
-
- - -
-
- - Identificación Industrial -
- -
-
- - -
-
-
- - -
-
- - Certificaciones y Seguridad -
- -
-
- - -
-
- -
- -

- Indica si cuenta con certificación de empresa -

-
-
-
-
-
-
-
- - - - - - Configuración - Ajustes de estado y atributos especiales. - - -
-
- -
- -

- Habilitar o deshabilitar este socio comercial -

-
-
-
-
-
-
-
-
+
+ +
+
+
+ +

+ {isEditing ? `Socio Comercial #${id}` : 'Nuevo Socio Comercial'} +

+ {#if isEditing} + + {formData.is_active ? 'Activo' : 'Inactivo'} + + {:else} + Nuevo + {/if}
+

+ {isEditing + ? 'Edita la información del cliente o proveedor' + : 'Registra un nuevo cliente o proveedor en el sistema'} +

- {/if} +
+ + + + +
+ +
{ + e.preventDefault(); + handleSubmit(); + }} + > + + + + + + Información General + Datos principales de identificación y clasificación. + + +
+
+ + +
+
+ + (formData.client_or_provider = v)} + > + + {typeLabels[formData.client_or_provider] || 'Selecciona un tipo'} + + + Cliente + Proveedor + Ambos + + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + (formData.type_nat_foreign = v)} + > + + {formData.type_nat_foreign === 'N' + ? 'Nacional' + : formData.type_nat_foreign === 'E' + ? 'Extranjero' + : 'Seleccione'} + + + Nacional + Extranjero + + +
+
+ +
+
+ + +
+
+ + +
+
+
+
+
+ + + + + + Dirección y Contacto + Ubicación fiscal y datos de contacto. + + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ + + +
+
+ + +
+
+ + +
+
+
+
+
+ + + + + + Programas y Certificaciones + Información sobre IMMEX, PROSEC y otras certificaciones. + + + +
+
+ + Programas de Fomento +
+ +
+
+ + (formData.program = v)} + disabled={loading} + > + + {formData.program || 'Selecciona un programa'} + + + {#each scaiiPrograms as prog} + + {prog.label} + + {/each} + + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+
+ + +
+
+ + Identificación Industrial +
+ +
+
+ + +
+
+
+ + +
+
+ + Certificaciones y Seguridad +
+ +
+
+ + +
+
+ +
+ +

+ Indica si cuenta con certificación de empresa +

+
+
+
+
+
+
+
+ + + + + + Configuración + Ajustes de estado y atributos especiales. + + +
+
+ +
+ +

+ Habilitar o deshabilitar este socio comercial +

+
+
+
+
+
+
+
+
+
-{#if canAccess} -
-
- - -
- - - General - - - Dirección - - - Programas - - - Config - - -
-
- - -
- - +
+
+ + +
+ + + General + + + Dirección + + + Programas + + + Config + +
+
+ + +
+ + {#if !isEditing} + + {/if} +
-{/if} +
- - (formData.country = c.code_3)} /> - (formData.state = s.code)} /> - (formData.prosec = sc.code)} /> + + (formData.country = country.m3_key)} +/> + + { + formData.state = state.description; + if (state.m3_key && !formData.country) { + formData.country = state.m3_key; + } + }} +/> + + (formData.prosec = sector.key)} +/> diff --git a/frontend/src/routes/dashboard/customs_brokers/+page.svelte b/frontend/src/routes/dashboard/customs_brokers/+page.svelte index 7eacd2b6..aabebb79 100644 --- a/frontend/src/routes/dashboard/customs_brokers/+page.svelte +++ b/frontend/src/routes/dashboard/customs_brokers/+page.svelte @@ -13,15 +13,13 @@ import { page } from '$app/stores'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosListaAgentes } from '$lib/config/shortcuts/dashboard/customs_brokers/list'; - import { authStore, userHasPermission } from '$lib/auth'; - import ErrorState from '$lib/components/dashboard/common/error-state.svelte'; import { customsSectionsApi, type CustomsSection } from '$lib/api/dashboard/reference_data/customs_sections'; import SectionsDataTable from '$lib/components/dashboard/reference_data/customs_sections/data-table.svelte'; - import { createColumns as createSectionColumns } from '$lib/components/dashboard/reference_data/customs_sections/columns.js'; + import { createColumns as createSectionColumns } from '$lib/components/dashboard/reference_data/customs_sections/columns'; import * as Card from '$lib/components/ui/card'; // Specialized Broker Components @@ -70,12 +68,6 @@ let sectionsPage = $state(1); let hasMoreSections = $derived(sections.length < totalSections); - // Permisos - const canView = $derived(userHasPermission($authStore.user, 'customs_brokers.view')); - const canCreate = $derived(userHasPermission($authStore.user, 'customs_brokers.create')); - const canEdit = $derived(userHasPermission($authStore.user, 'customs_brokers.edit')); - const canDelete = $derived(userHasPermission($authStore.user, 'customs_brokers.delete')); - // --- Lifecycle --- onMount(() => { if (browser) { @@ -196,266 +188,256 @@
- {#if !canView} - window.location.reload()} - /> - {:else} -
-
-

Gestión Aduanal

-

Administración de Agentes y Secciones Aduanales

-
-
- {#if canCreate} - - {/if} - {#if canEdit} - - {/if} - {#if canDelete} - - {/if} -
+
+
+

Gestión Aduanal

+

Administración de Agentes y Secciones Aduanales

+
- - - - Agentes Aduanales - - - Secciones Aduanales - - - - + + -
-
-
-
-

Filtros

- Busque por nombre o patente -
-
-
- - -
-
- - -
-
-
-
-
+ Agentes Aduanales + + + Secciones Aduanales + + -
-
-

Listado

-
- - {filteredItems.length} registros - - -
+ +
+
+
+
+

Filtros

+ Busque por nombre o patente
- -
- -
- {#if totalItems > pageSize} -
- - - Página {currentPage} de {Math.ceil(totalItems / pageSize)} - - +
+
+ +
- {/if} +
+ + +
+
+
-
-
-

- Detalles del Agente -

-

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

-
- Patente: {selectedItem?.broker_key || ''} +
+
+

Listado

+
+ + {filteredItems.length} registros + +
- - {#if selectedItem} - - {/if}
-
- {#if selectedItem} -
+
+ +
+ {#if totalItems > pageSize} +
+ + + Página {currentPage} de {Math.ceil(totalItems / pageSize)} + + +
+ {/if} +
+
+ +
+
+

+ Detalles del Agente +

+

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

+
+ Patente: {selectedItem?.broker_key || ''} +
+
+ +
+ {#if selectedItem} +
+
+ +

{selectedItem.license || '-'}

+
+ + {#if selectedItem.tax_id}
-

{selectedItem.license || '-'}

+

{selectedItem.tax_id}

+ {/if} - {#if selectedItem.tax_id} -
- -

{selectedItem.tax_id}

+
+ +
+

{selectedItem.address || ''}

+

+ {[selectedItem.city, selectedItem.state].filter(Boolean).join(', ')} +

+

+ {[selectedItem.postal_code, selectedItem.country].filter(Boolean).join(', ')} +

+
+
+ +
+ + {#if selectedItem.email} +
+ + {selectedItem.email}
{/if} - -
- -
-

{selectedItem.address || ''}

-

- {[selectedItem.city, selectedItem.state].filter(Boolean).join(', ')} -

-

- {[selectedItem.postal_code, selectedItem.country].filter(Boolean).join(', ')} -

+ {#if selectedItem.phone} +
+ + {selectedItem.phone}
-
- -
- - {#if selectedItem.email} -
- - {selectedItem.email} -
- {/if} - {#if selectedItem.phone} -
- - {selectedItem.phone} -
- {/if} - {#if selectedItem.contact} -
- Contacto: - {selectedItem.contact} -
- {/if} -
+ {/if} + {#if selectedItem.contact} +
+ Contacto: + {selectedItem.contact} +
+ {/if}
- {:else} -
- -

Selecciona un agente

-
- {/if} -
+
+ {:else} +
+ +

Selecciona un agente

+
+ {/if}
- +
+ - - - - - - - - + + + + + + + + -
- {/if} +
+
+
+
+
+ {#if activeTab === 'brokers'} + + + + {:else} + + {/if} +
+
diff --git a/frontend/src/routes/dashboard/customs_brokers/edit/[[id]]/+page.svelte b/frontend/src/routes/dashboard/customs_brokers/edit/[[id]]/+page.svelte index 6174b0f1..f08dcc61 100644 --- a/frontend/src/routes/dashboard/customs_brokers/edit/[[id]]/+page.svelte +++ b/frontend/src/routes/dashboard/customs_brokers/edit/[[id]]/+page.svelte @@ -1,8 +1,5 @@ -
- {#if !canAccess} - window.location.reload()} - onBack={() => goto('/dashboard/customs_brokers')} - /> - {:else} - -
- -
-
-
- -

- {isEdit ? `Agente ${formData.broker_key}` : 'Nuevo Agente Aduanal'} -

- - {isEdit ? 'Edición' : 'Nuevo'} - -
-

- {isEdit - ? 'Modifica la información del agente aduanal' - : 'Registra un nuevo agente aduanal en el sistema'} -

-
-
- - + +
+ +
+
+
+ +

+ {isEdit ? `Agente ${formData.broker_key}` : 'Nuevo Agente Aduanal'} +

+ + {isEdit ? 'Edición' : 'Nuevo'} +
+

+ {isEdit + ? 'Modifica la información del agente aduanal' + : 'Registra un nuevo agente aduanal en el sistema'} +

+
+
- -
-
{ - e.preventDefault(); - handleSave(); - }} - > - - - - - Información General - Identificación oficial del agente y patente. - - -
-
- - (formData.type = v)} - disabled={loading} - > - - {formData.type === 'MEX' - ? 'Agente Aduanal Mexicano' - : formData.type === 'USA' - ? 'Agente Aduanal Americano (Broker)' - : 'Selecciona un tipo...'} - - - Agente Aduanal Mexicano - Agente Aduanal Americano (Broker) - - -
-
+ -
-
- - { - const val = e.currentTarget.value.toUpperCase(); - if (val.length > 5) { - brokerKeyError = true; - formData.broker_key = val.slice(0, 5); - e.currentTarget.value = formData.broker_key; - - clearTimeout(brokerKeyTimeout); - brokerKeyTimeout = setTimeout(() => { - brokerKeyError = false; - }, 3000); - } else { - brokerKeyError = false; - formData.broker_key = val; - } - }} - placeholder="Ej. 550" - maxlength={6} - class={`h-10 ${brokerKeyError ? 'border-red-500 focus-visible:ring-red-500' : ''}`} - disabled={isEdit || loading} - /> - {#if brokerKeyError} -

- La clave no debe superar los 5 caracteres -

- {/if} -
-
- - { - const val = e.currentTarget.value.replace(/\D/g, ''); - if (val.length > 4) { - licenseError = true; - formData.license = val.slice(0, 4); - e.currentTarget.value = formData.license; - - clearTimeout(licenseTimeout); - licenseTimeout = setTimeout(() => { - licenseError = false; - }, 3000); - } else { - licenseError = false; - formData.license = val; - } - }} - placeholder="Ej. 3421" - maxlength={5} - class={`h-10 ${licenseError ? 'border-red-500 focus-visible:ring-red-500' : ''}`} - disabled={loading} - /> - {#if licenseError} -

- La patente no debe superar los 4 dígitos -

- {/if} -
-
- - - -
- - -
- -
-
- - { - formData.tax_id = e.currentTarget.value - .toUpperCase() - .replace(/[^A-Z0-9&Ñ]/g, '') - .slice(0, 13); - e.currentTarget.value = formData.tax_id; - }} - placeholder="RFC de la empresa" - disabled={loading} - class="h-10" - /> -
-
- - { - formData.personal_id = e.currentTarget.value - .toUpperCase() - .replace(/[^A-Z0-9]/g, '') - .slice(0, 18); - e.currentTarget.value = formData.personal_id; - }} - placeholder="CURP si aplica" - disabled={loading} - class="h-10" - /> -
-
-
-
-
- - - - - - Información de Contacto - Datos para comunicación con el agente. - - -
-
- - { - formData.contact = e.currentTarget.value.replace( - /[^a-zA-Z0-9\sñÑáéíóúÁÉÍÓÚ\-\.,]/g, - '' - ); - e.currentTarget.value = formData.contact; - }} - placeholder="Nombre del contacto" - disabled={loading} - class="h-10" - /> -
-
- - -
-
- - - -
-
- - { - formData.phone = e.currentTarget.value.replace(/[^\d\s\-\+\(\)]/g, ''); - e.currentTarget.value = formData.phone; - }} - placeholder="656-000-0000" - disabled={loading} - class="h-10" - /> -
-
- - { - formData.fax = e.currentTarget.value.replace(/[^\d\s\-\+\(\)]/g, ''); - e.currentTarget.value = formData.fax; - }} - disabled={loading} - class="h-10" - /> -
-
- - -
-
-
-
-
- - - - - - Domicilio Fiscal - Ubicación registrada del agente aduanal. - - -
- - -
-
-
- - { - formData.postal_code = e.currentTarget.value.replace(/[^a-zA-Z0-9]/g, ''); - }} - /> -
-
- - -
-
-
-
- -
- (showStateDialog = true)} - onkeydown={(event) => - openOnEnterOrSpace(event, () => (showStateDialog = true))} - /> - -
-
-
- -
- (showCountryDialog = true)} - onkeydown={(event) => - openOnEnterOrSpace(event, () => (showCountryDialog = true))} - /> - -
-
-
-
-
- - -
- - - - - Ventanilla Única / Web Services - Certificados y credenciales para integración con DODA/PITA. +
+ { + e.preventDefault(); + handleSave(); + }} + > + + + + + Información General + Identificación oficial del agente y patente. + + +
+
+ + (formData.type = v)} + disabled={loading} > - - -
-
- - -
-
- - -
-
- - - -
-
- -
- (pendingVuFiles.certificate = file)} - disabled={loading} - /> - {#if vuData.certificate_path} - - {getFileDisplayName(vuData.certificate_path)} - - {/if} -
-
-
- -
- (pendingVuFiles.key = file)} - disabled={loading} - /> - {#if vuData.key_path} - - {getFileDisplayName(vuData.key_path)} - - {/if} -
-
-
- -
-
- - -
-
- - -
-
-
- - - - - - - DODA / PITA - Credenciales exclusivas para el servicio DODA. - - -
-
- - -
-
- - -
+ + {formData.type === 'MEX' + ? 'Agente Aduanal Mexicano' + : formData.type === 'USA' + ? 'Agente Aduanal Americano (Broker)' + : 'Selecciona un tipo...'} + + + Agente Aduanal Mexicano + Agente Aduanal Americano (Broker) + +
- -
-
- -
- (pendingVuFiles.dodaCertificate = file)} - disabled={loading} - /> - {#if vuData.doda_certificate_path} - - {getFileDisplayName(vuData.doda_certificate_path)} - - {/if} -
-
-
- -
- (pendingVuFiles.dodaKey = file)} - disabled={loading} - /> - {#if vuData.doda_key_path} - - {getFileDisplayName(vuData.doda_key_path)} - - {/if} -
-
-
-
-
-
+
- - - - ANAM / Otros Servicios - Configuraciones de rutas y archivos locales. - - -
-
- - -
-
- - -
+
+
+ + { + const val = e.currentTarget.value.toUpperCase(); + if (val.length > 5) { + brokerKeyError = true; + formData.broker_key = val.slice(0, 5); + e.currentTarget.value = formData.broker_key; + + clearTimeout(brokerKeyTimeout); + brokerKeyTimeout = setTimeout(() => { + brokerKeyError = false; + }, 3000); + } else { + brokerKeyError = false; + formData.broker_key = val; + } + }} + placeholder="Ej. 550" + maxlength={6} + class={`h-10 ${brokerKeyError ? 'border-red-500 focus-visible:ring-red-500' : ''}`} + disabled={isEdit || loading} + /> + {#if brokerKeyError} +

+ La clave no debe superar los 5 caracteres +

+ {/if}
- + { + const val = e.currentTarget.value.replace(/\D/g, ''); + if (val.length > 4) { + licenseError = true; + formData.license = val.slice(0, 4); + e.currentTarget.value = formData.license; + + clearTimeout(licenseTimeout); + licenseTimeout = setTimeout(() => { + licenseError = false; + }, 3000); + } else { + licenseError = false; + formData.license = val; + } + }} + placeholder="Ej. 3421" + maxlength={5} + class={`h-10 ${licenseError ? 'border-red-500 focus-visible:ring-red-500' : ''}`} + disabled={loading} + /> + {#if licenseError} +

+ La patente no debe superar los 4 dígitos +

+ {/if} +
+
+ + + +
+ + +
+ +
+
+ + { + formData.tax_id = e.currentTarget.value + .toUpperCase() + .replace(/[^A-Z0-9&Ñ]/g, '') + .slice(0, 13); + e.currentTarget.value = formData.tax_id; + }} + placeholder="RFC de la empresa" disabled={loading} class="h-10" />
- - - - -
- - {/if} -
+
+ + { + formData.personal_id = e.currentTarget.value + .toUpperCase() + .replace(/[^A-Z0-9]/g, '') + .slice(0, 18); + e.currentTarget.value = formData.personal_id; + }} + placeholder="CURP si aplica" + disabled={loading} + class="h-10" + /> +
+
+
+
+
- -{#if canAccess} + + + + + Información de Contacto + Datos para comunicación con el agente. + + +
+
+ + { + formData.contact = e.currentTarget.value.replace( + /[^a-zA-Z0-9\sñÑáéíóúÁÉÍÓÚ\-\.,]/g, + '' + ); + e.currentTarget.value = formData.contact; + }} + placeholder="Nombre del contacto" + disabled={loading} + class="h-10" + /> +
+
+ + +
+
+ + + +
+
+ + { + formData.phone = e.currentTarget.value.replace(/[^\d\s\-\+\(\)]/g, ''); + e.currentTarget.value = formData.phone; + }} + placeholder="656-000-0000" + disabled={loading} + class="h-10" + /> +
+
+ + { + formData.fax = e.currentTarget.value.replace(/[^\d\s\-\+\(\)]/g, ''); + e.currentTarget.value = formData.fax; + }} + disabled={loading} + class="h-10" + /> +
+
+ + +
+
+
+
+
+ + + + + + Domicilio Fiscal + Ubicación registrada del agente aduanal. + + +
+ + +
+
+
+ + { + formData.postal_code = e.currentTarget.value.replace(/[^a-zA-Z0-9]/g, ''); + }} + /> +
+
+ + +
+
+
+
+ +
+ (showStateDialog = true)} + onkeydown={(event) => + openOnEnterOrSpace(event, () => (showStateDialog = true))} + /> + +
+
+
+ +
+ (showCountryDialog = true)} + onkeydown={(event) => + openOnEnterOrSpace(event, () => (showCountryDialog = true))} + /> + +
+
+
+
+
+ + +
+ + + + + Ventanilla Única / Web Services + Certificados y credenciales para integración con DODA/PITA. + + +
+
+ + { + vuData.certificate_path = file.name; + pendingVuFiles.certificate = file; + toast.success(`Archivo ${file.name} seleccionado`); + }} + /> +
+
+ + { + vuData.key_path = file.name; + pendingVuFiles.key = file; + toast.success(`Archivo ${file.name} seleccionado`); + }} + /> +
+
+ +
+
+ + +
+
+ + + + {vuData.vu_figure_type || 'Seleccionar tipo de figura'} + + + AGENTE ADUANAL + APODERADO ADUANAL + MANDATARIO + + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + { + vuData.xml_files_path = file.name; + pendingVuFiles.cove = file; + toast.success(`Archivo ${file.name} seleccionado`); + }} + /> +
+
+ + + +
+

+ Configuración Adicional +

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+ + + + + + DODA-PITA + Configuración de servicios DODA / PITA. + + +
+
+ + +
+
+ + +
+
+ +
+
+ + { + vuData.doda_certificate_path = file.name; + pendingVuFiles.dodaCertificate = file; + toast.success(`Archivo ${file.name} seleccionado`); + }} + /> +
+
+ + { + vuData.doda_key_path = file.name; + pendingVuFiles.dodaKey = file; + toast.success(`Archivo ${file.name} seleccionado`); + }} + /> +
+
+ +
+
+ + +
+
+ + { + vuData.doda_xml_files_path = file.name; + pendingVuFiles.dodaCove = file; + toast.success(`Archivo ${file.name} seleccionado`); + }} + /> +
+
+
+
+
+ + + + ANAM + Configuración de acceso para ANAM. + + +
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+ +
- - -
- - - General - - - Contacto - - - Dirección - - - VU - - - DODA - - - ANAM - - -
-
+ +
+ + General + Contacto + Domicilio + VU + DODA + ANAM + +
- - +
-{/if} + diff --git a/frontend/src/routes/dashboard/digitalizacion/+page.svelte b/frontend/src/routes/dashboard/despacho/digitalizacion/+page.svelte similarity index 87% rename from frontend/src/routes/dashboard/digitalizacion/+page.svelte rename to frontend/src/routes/dashboard/despacho/digitalizacion/+page.svelte index 9262cdf0..7a04901a 100644 --- a/frontend/src/routes/dashboard/digitalizacion/+page.svelte +++ b/frontend/src/routes/dashboard/despacho/digitalizacion/+page.svelte @@ -7,7 +7,9 @@ import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; - import { Plus, RefreshCw, FileCheck2, Download, FolderArchive, Pencil, Trash2 } from 'lucide-svelte'; + import { Label } from '$lib/components/ui/label'; + import * as Select from '$lib/components/ui/select'; + import { Plus, RefreshCw, FileCheck2, Download, FolderArchive, Pencil, Trash2, Filter } from 'lucide-svelte'; import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte'; import CreateEditDialog from '$lib/components/dashboard/digitalizacion/create-edit-dialog.svelte'; @@ -31,6 +33,10 @@ let hasMore = $derived(data.length < totalItems); let search = $state($page.url.searchParams.get('search') || ''); + let searchEDocument = $state(''); + let searchRFC = $state(''); + let searchStatus = $state('all'); + let showFilters = $state(false); let searchTimeout: ReturnType; // Selección de filas @@ -63,7 +69,10 @@ const canOpenAcuse = $derived(selectedItem?.status === 'success' && !!selectedIt const res = await expedienteArchivosApi.list(companyStore.activeCompany.id, { page: 1, page_size: pageSize, - search: search || undefined + search: search || undefined, + e_document: searchEDocument || undefined, + rfc_consulta: searchRFC || undefined, + status: searchStatus === 'all' ? undefined : searchStatus }); if (res.data) { data = res.data.items; @@ -85,7 +94,10 @@ const canOpenAcuse = $derived(selectedItem?.status === 'success' && !!selectedIt const res = await expedienteArchivosApi.list(companyStore.activeCompany.id, { page: currentPage + 1, page_size: pageSize, - search: search || undefined + search: search || undefined, + e_document: searchEDocument || undefined, + rfc_consulta: searchRFC || undefined, + status: searchStatus === 'all' ? undefined : searchStatus }); if (res.data?.items) { data = [...data, ...res.data.items]; @@ -393,6 +405,15 @@ async function handleDownloadArtifact(
{m['sidebar.digitalizacion.table_title']()}
+
+ + {#if showFilters} +
+ +
+ + +
+ + +
+ + +
+ + +
+ + { + searchStatus = v || 'all'; + loadData(); + }} + > + + + + + Todos + Pendiente + Procesando + Completado + Fallido + + +
+ + +
+ +
+
+ {/if} {#if loading && data.length === 0} diff --git a/frontend/src/routes/dashboard/despacho/doda/+page.server.ts b/frontend/src/routes/dashboard/despacho/doda/+page.server.ts index 96caa12d..8d3a8b86 100644 --- a/frontend/src/routes/dashboard/despacho/doda/+page.server.ts +++ b/frontend/src/routes/dashboard/despacho/doda/+page.server.ts @@ -2,67 +2,80 @@ import type { PageServerLoad } from './$types'; import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { - const parentData = await parent(); - const { accessToken } = getAuthTokens(cookies); + const parentData = await parent(); + const { accessToken } = getAuthTokens(cookies); - if (!accessToken) { - return { - error: 'No authenticated', - dodas: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } - }; - } + const pUser = parentData.user as + | { preferred_username?: string; name?: string; email?: string } + | undefined + | null; + const defaultLastUser = + (pUser?.preferred_username?.trim() || pUser?.name?.trim() || pUser?.email?.trim() || '') || ''; - try { - const page = Number(url.searchParams.get('page')) || 1; - const pageSize = Number(url.searchParams.get('pageSize')) || 50; + if (!accessToken) { + return { + error: 'No authenticated', + dodas: { items: [], total: 0, page: 1, page_size: 50, pages: 0 }, + defaultLastUser + }; + } - const cookieCompanyId = cookies.get('active_company_id'); - const companyId = cookieCompanyId - ? parseInt(cookieCompanyId) - : parentData.companies?.[0]?.id; + try { + const page = Number(url.searchParams.get('page')) || 1; + const pageSize = Number(url.searchParams.get('pageSize')) || 50; + + // Obtener company_id de la cookie o usar el primero disponible + const cookieCompanyId = cookies.get('active_company_id'); + const companyId = cookieCompanyId + ? parseInt(cookieCompanyId) + : parentData.companies?.[0]?.id; - if (!companyId) { - return { - error: 'No company selected', - dodas: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } - }; - } + if (!companyId) { + return { + error: 'No company selected', + dodas: { items: [], total: 0, page: 1, page_size: 50, pages: 0 }, + defaultLastUser + }; + } - const filters: Record = {}; - const integrationNumber = url.searchParams.get('integration_number'); - const patent = url.searchParams.get('patent'); - const status = url.searchParams.get('status'); - const operationType = url.searchParams.get('operation_type'); + const filters: Record = {}; + const integrationNumber = url.searchParams.get('integration_number'); - if (integrationNumber) filters.integration_number = integrationNumber; - if (patent) filters.patent = patent; - if (status) filters.status = status; - if (operationType) filters.operation_type = operationType; + if (integrationNumber) filters.integration_number = integrationNumber; - const queryParams = new URLSearchParams({ - page: page.toString(), - page_size: pageSize.toString(), - company_id: companyId.toString(), - ...filters - }); + const queryParams = new URLSearchParams({ + page: page.toString(), + page_size: pageSize.toString(), + company_id: companyId.toString(), + ...filters + }); + const response = await authenticatedFetch(`v1/a76/doda?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch); - const response = await authenticatedFetch( - `v1/a76/doda?${queryParams.toString()}`, - { method: 'GET' }, - cookies, - fetch - ); + if (!response.ok) { + let errorMsg = 'Failed to load'; + try { + const errorData = await response.json(); + errorMsg = errorData.detail || errorData.message || errorMsg; + } catch (e) { + // Ignore json parsing error + } + return { + error: errorMsg, + status: response.status, + dodas: { items: [], total: 0, page, page_size: pageSize, pages: 0 }, + defaultLastUser + }; + } - if (!response.ok) { - return { - error: 'Failed to load', - dodas: { items: [], total: 0, page, page_size: pageSize, pages: 0 } - }; - } - - return { dodas: await response.json() }; - } catch (error) { - console.error('Error loading DODAs:', error); - return { error: 'Error loading', dodas: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } }; - } + const dodasData = await response.json(); + return { dodas: dodasData, status: 200, defaultLastUser }; + } catch (error: any) { + console.error('Error loading DODAs:', error); + return { + error: error.message || 'Error loading', + status: error.status || 500, + dodas: { items: [], total: 0, page: 1, page_size: 50, pages: 0 }, + defaultLastUser + }; + } }; diff --git a/frontend/src/routes/dashboard/despacho/doda/+page.svelte b/frontend/src/routes/dashboard/despacho/doda/+page.svelte index b5b9126e..0aa358f9 100644 --- a/frontend/src/routes/dashboard/despacho/doda/+page.svelte +++ b/frontend/src/routes/dashboard/despacho/doda/+page.svelte @@ -1,414 +1,357 @@ -
-
-
-

{m['sidebar.doda_alta.title']()}

-

{m['sidebar.doda_alta.subtitle']()}

-
-
- - -
-
- - - -
- {m['sidebar.doda_alta.table_title']()} -
-
- - -
- - (filters.status = v)} - > - - {filters.status || m['sidebar.doda_alta.filter_status']()} - - - Todos - PENDIENTE - GENERADO - VALIDADO - ELIMINADO - - - (filters.operation_type = v)} - > - - {filters.operation_type === 'I' - ? 'Importación' - : filters.operation_type === 'E' - ? 'Exportación' - : m['sidebar.doda_alta.filter_operation_type']()} - - - Todas - I - Importación - E - Exportación - - - -
-
-
- -
- { - selectedDodaIds = selectedDodaIds.includes(row.id) ? [] : [row.id]; - }} - onRowDoubleClick={(item) => - goto(`/dashboard/general_catalogs/doda?doda_id=${item.id}`, { noScroll: true })} - /> -
-
-
- -
- Mostrando {allDodas.length} de {dodaTotal} registros - - Filtros activos: {Object.values(filters).filter((v) => v !== '').length} -
- -
- -
-
-
- - - -