From e5f6162ffb98b1bc591d95ba142bdf45f514ec37 Mon Sep 17 00:00:00 2001 From: acazares Date: Thu, 11 Dec 2025 11:30:44 -0600 Subject: [PATCH] feat: Implement invoice management features including data table, dialogs for viewing, editing, and deleting invoices, and server-side loading of invoice data --- backend/api/v1/modules/a76/invoices/models.py | 157 ++-- .../api/v1/modules/a76/invoices/schemas.py | 43 +- frontend/messages/en.json | 12 + frontend/messages/es.json | 12 + .../src/lib/api/dashboard/a76/invoices.ts | 318 ++++++++ .../components/dashboard/invoices/columns.ts | 89 +++ .../invoices/create-edit-dialog.svelte | 677 ++++++++++++++++++ .../invoices/data-table-actions.svelte | 52 ++ .../dashboard/invoices/data-table.svelte | 131 ++++ .../dashboard/invoices/delete-dialog.svelte | 110 +++ .../dashboard/invoices/details-dialog.svelte | 435 +++++++++++ .../src/lib/components/sidebar/modules.ts | 40 ++ .../routes/dashboard/invoices/+page.server.ts | 104 +++ .../routes/dashboard/invoices/+page.svelte | 283 ++++++++ 14 files changed, 2381 insertions(+), 82 deletions(-) create mode 100644 frontend/src/lib/api/dashboard/a76/invoices.ts create mode 100644 frontend/src/lib/components/dashboard/invoices/columns.ts create mode 100644 frontend/src/lib/components/dashboard/invoices/create-edit-dialog.svelte create mode 100644 frontend/src/lib/components/dashboard/invoices/data-table-actions.svelte create mode 100644 frontend/src/lib/components/dashboard/invoices/data-table.svelte create mode 100644 frontend/src/lib/components/dashboard/invoices/delete-dialog.svelte create mode 100644 frontend/src/lib/components/dashboard/invoices/details-dialog.svelte create mode 100644 frontend/src/routes/dashboard/invoices/+page.server.ts create mode 100644 frontend/src/routes/dashboard/invoices/+page.svelte diff --git a/backend/api/v1/modules/a76/invoices/models.py b/backend/api/v1/modules/a76/invoices/models.py index bed62eaf..7f787def 100644 --- a/backend/api/v1/modules/a76/invoices/models.py +++ b/backend/api/v1/modules/a76/invoices/models.py @@ -1,6 +1,6 @@ from enum import Enum from typing import Optional, List -from sqlalchemy import BigInteger, Date, ForeignKey, Integer, Numeric, String, Text, TIMESTAMP +from sqlalchemy import BigInteger, Boolean, Date, ForeignKey, Integer, Numeric, String, Text, TIMESTAMP from sqlalchemy.orm import Mapped, mapped_column, relationship from core.database import Base from datetime import datetime @@ -11,21 +11,35 @@ class OperationType(str, Enum): IMP = "imp" # Importación EXP = "exp" # Exportación + +class TransportType(str, Enum): + NONE = "none" # Ninguno + TRANSPORT = "transport" # Transporte + BOX = "box" # Caja + PLATES = "licence plates" # Placas + TRUCK = "truck" # Camión + VESSEL = "vessel" # Buque + BARGE = "rail barge" # Ferrobarcaza + CONTAINER = "container" # Contenedor + AIRPLANE = "airplane" # Avión + GONDOLA = "gondola" # Góndola + FLATBED = "flatbed" # Plataforma + # --- 1. Invoice Header (invoice_header) --- - - class InvoiceHeader(Base, TenantScopedMixin, TimestampMixin): __tablename__ = "invoice_header" - __tableargs__ = {'schema': 'a76'} + __table_args__ = ( + {"schema": "a76"}, + ) id: Mapped[int] = mapped_column( BigInteger, primary_key=True, autoincrement=True) # Identifiers operation_type: Mapped[OperationType] = mapped_column( - String(3)) # TIPOMOVIMIENTO / Clasifica IMP/EXP - invoice_type: Mapped[Optional[str]] = mapped_column( - ForeignKey("public.invoice_types.key")) # TIPOFACTURA + String(3)) # TIPOMOVIMIENTO / Clasifica imp/exp + invoice_type: Mapped[Optional[str]] = mapped_column(ForeignKey( + "public.invoice_types.key")) # TIPOFACTURA (invoice_types) invoice_number: Mapped[Optional[str]] = mapped_column( String(20)) # FACTURAIMPO/FACTURAEXPO/FACTURAREMISION project_number: Mapped[Optional[str]] = mapped_column( @@ -42,20 +56,22 @@ class InvoiceHeader(Base, TenantScopedMixin, TimestampMixin): TIMESTAMP(timezone=False), default=datetime.now) # FECHACAPTURA + HORAACTUAL # Status & Control - status: Mapped[Optional[str]] = mapped_column(String(5)) # ESTATUS - traffic_light_status: Mapped[Optional[str]] = mapped_column( - String(50)) # SEMAFORO / SEMAFOROEXPO/IMPO - process_log: Mapped[Optional[str]] = mapped_column( - String(300)) # COMOFUEPROCESADA **<-- AÑADIDO** - # OBSERVACIONE + OBSERVACIONI + COMENTARIOSESTATUS - comments: Mapped[Optional[str]] = mapped_column(Text) + is_updated: Mapped[Optional[str]] = mapped_column(Boolean) # ESTATUS + updated_date: Mapped[Optional[datetime]] = mapped_column(TIMESTAMP(timezone=False)) # FECHAACTUALIZACION + who_updated: Mapped[Optional[str]] = mapped_column(String(20)) # QUIENA ACTUALIZO + + traffic_light_status: Mapped[Optional[str]] = mapped_column(String(50)) # SEMAFORO / SEMAFOROEXPO/IMPO + process_log: Mapped[Optional[str]] = mapped_column(String(300)) # COMOFUEPROCESADA + + # Comments + observation_es: Mapped[Optional[str]] = mapped_column(Text) #OBSERVACIONE + observation_en: Mapped[Optional[str]] = mapped_column(Text) #OBSERVACIONI + comments_status: Mapped[Optional[str]] = mapped_column(Text) #COMENTARIOSESTATUS # Digital Archive Links cfdi_uuid: Mapped[Optional[str]] = mapped_column(String(100)) # CFDIUUID - path_pdf: Mapped[Optional[str]] = mapped_column( - String(500)) # CFDIPATHPDF **<-- AÑADIDO** - path_xml: Mapped[Optional[str]] = mapped_column( - String(500)) # CFDIPATHXML **<-- AÑADIDO** + path_pdf: Mapped[Optional[str]] = mapped_column(String(500)) # CFDIPATHPDF + path_xml: Mapped[Optional[str]] = mapped_column(String(500)) # CFDIPATHXML # Relationships (Para navegacion ORM) compliance_mx: Mapped["InvoiceComplianceMx"] = relationship( @@ -73,10 +89,12 @@ class InvoiceHeader(Base, TenantScopedMixin, TimestampMixin): # --- 2. Compliance MX (invoice_compliance_mx) --- class InvoiceComplianceMx(Base, TenantScopedMixin, TimestampMixin): __tablename__ = "invoice_compliance_mx" - __tableargs__ = {'schema': 'a76'} + __table_args__ = ( + {"schema": "a76"}, + ) invoice_id: Mapped[int] = mapped_column( - ForeignKey("invoice_header.id"), primary_key=True) + ForeignKey("a76.invoice_header.id"), primary_key=True) # Core Customs Data pedimento: Mapped[Optional[str]] = mapped_column( @@ -84,23 +102,29 @@ class InvoiceComplianceMx(Base, TenantScopedMixin, TimestampMixin): pedimento_code: Mapped[Optional[str]] = mapped_column( String(5)) # PEDIMENTOR1, K1 remesa: Mapped[Optional[int]] = mapped_column(Integer) - aduana: Mapped[Optional[str]] = mapped_column(String(5)) # ADUANA_CRUCE - customs_agent: Mapped[Optional[str]] = mapped_column( - String(10)) # AADUANAL + aduana: Mapped[Optional[str]] = mapped_column(ForeignKey("public.customs_sections.customs_code")) # ADUANA_CRUCE + + # Clients & Providers + provider_header: Mapped[Optional[str]] = mapped_column(String(20)) # PROVEEDOREXPORTADOR + provider_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # PROVEEDOR + sold_to_header: Mapped[Optional[str]] = mapped_column(String(20)) # VENDIDOCONSIGNADO + sold_to_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # VENDIDOA + shipped_to_header: Mapped[Optional[str]] = mapped_column(String(20)) # ENVIADOTRANSFERIDO + shipped_to_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # ENVIADOA + shipped_by_header: Mapped[Optional[str]] = mapped_column(String(20)) # ENVIADOPORVENDIDOPOR + shipped_by_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # VENDIDOPOR + + customs_broker_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.customs_brokers.id")) # AADUANAL # Flags & Specific Regimes - is_mixed: Mapped[Optional[str]] = mapped_column( - String(2)) # ESMIXTO **<-- AÑADIDO** - waste_type: Mapped[Optional[str]] = mapped_column( - String(1)) # TIPODESPERDICIO **<-- AÑADIDO** + is_mixed: Mapped[Optional[bool]] = mapped_column(Boolean) # ESMIXTO + waste_type: Mapped[Optional[str]] = mapped_column(String(1)) # TIPODESPERDICIO appendix_17: Mapped[Optional[int]] = mapped_column(Integer) # APENDICE17 # VUCEM / Digital edocument: Mapped[Optional[str]] = mapped_column(String(50)) # EDOCUMENT - electronic_signature: Mapped[Optional[str]] = mapped_column( - String(999)) # FIRMAELECTRONICA - sem_id: Mapped[Optional[int]] = mapped_column( - Integer) # SEM (de SFacEntradaSM/SFacSalidaSM) + electronic_signature: Mapped[Optional[str]] = mapped_column(String(999)) # FIRMAELECTRONICA + sem_id: Mapped[Optional[int]] = mapped_column(Integer) # SEM (de SFacEntradaSM/SFacSalidaSM) # Relationship header: Mapped["InvoiceHeader"] = relationship( @@ -110,40 +134,33 @@ class InvoiceComplianceMx(Base, TenantScopedMixin, TimestampMixin): # --- 3. Financials (invoice_financials) --- class InvoiceFinancials(Base, TenantScopedMixin, TimestampMixin): __tablename__ = "invoice_financials" - __tableargs__ = {'schema': 'a76'} + __table_args__ = ( + {"schema": "a76"}, + ) - invoice_id: Mapped[int] = mapped_column( - ForeignKey("invoice_header.id"), primary_key=True) + id: Mapped[int] = mapped_column( + BigInteger, primary_key=True, autoincrement=True) + invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id")) currency: Mapped[Optional[str]] = mapped_column(String(3)) # CLAVEMONEDA - exchange_rate: Mapped[Optional[float]] = mapped_column( - Numeric(13, 6)) # TIPOCAMBIO + currency_type: Mapped[Optional[str]] = mapped_column(ForeignKey("public.currency_types.code")) # TIPOCLAVEMONEDA + exchange_rate: Mapped[Optional[float]] = mapped_column(Numeric(13, 6)) # TIPOCAMBIO # Merchandise Values - value_mn: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0) # VALORIMPOMN/EXPOMN - value_me: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0) - customs_value_mn: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0) # VALORADUANASMN **<-- AÑADIDO** + value_mn: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # VALORIMPOMN/EXPOMN + value_me: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) + customs_value_mn: Mapped[Optional[float]] = mapped_column( Numeric(23, 8), default=0) # VALORADUANASMN # Costs & Taxes - freight: Mapped[Optional[float]] = mapped_column( - Numeric(19, 8), default=0) # FLETE - insurance: Mapped[Optional[float]] = mapped_column( - Numeric(19, 8), default=0) # VALSEGUROS/SEGUROS - iva_mn: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0) # IVAEXPOMN / VALORIVAMN - iva_factor: Mapped[Optional[float]] = mapped_column( - Numeric(17, 4)) # FACTORIVA + freight: Mapped[Optional[float]] = mapped_column(Numeric(19, 8), default=0) # FLETE + insurance: Mapped[Optional[float]] = mapped_column(Numeric(19, 8), default=0) # VALSEGUROS/SEGUROS + iva_mn: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # IVAEXPOMN / VALORIVAMN + iva_factor: Mapped[Optional[float]] = mapped_column(Numeric(17, 4)) # FACTORIVA # Weights & Quantities - total_quantity: Mapped[Optional[float]] = mapped_column( - Numeric(19, 8)) # CANTEXPO / CANTIMPO - gross_weight: Mapped[Optional[float]] = mapped_column( - Numeric(19, 8)) # PESOBRUTO **<-- AÑADIDO** - net_weight: Mapped[Optional[float]] = mapped_column( - Numeric(19, 8)) # PESONETO **<-- AÑADIDO** + total_quantity: Mapped[Optional[float]] = mapped_column(Numeric(19, 8)) # CANTEXPO / CANTIMPO + gross_weight: Mapped[Optional[float]] = mapped_column(Numeric(19, 8)) # PESOBRUTO + net_weight: Mapped[Optional[float]] = mapped_column(Numeric(19, 8)) # PESONETO bundle_count: Mapped[Optional[int]] = mapped_column(Integer) # CANTBULTOS # Relationship @@ -153,17 +170,21 @@ class InvoiceFinancials(Base, TenantScopedMixin, TimestampMixin): # --- 4. Logistics (invoice_logistics) --- class InvoiceLogistics(Base, TenantScopedMixin, TimestampMixin): __tablename__ = "invoice_logistics" - __tableargs__ = {'schema': 'a76'} + __table_args__ = ( + {"schema": "a76"}, + ) - logistics_id: Mapped[int] = mapped_column( + id: Mapped[int] = mapped_column( BigInteger, primary_key=True, autoincrement=True) - invoice_id: Mapped[int] = mapped_column(ForeignKey("invoice_header.id")) + invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id")) # Carrier Info carrier_id: Mapped[Optional[str]] = mapped_column( String(10)) # TRANSPORTISTA + transport_type: Mapped[TransportType] = mapped_column( + String(15), default="none") # TRANSPORTE transport_mode: Mapped[Optional[str]] = mapped_column( - String(15)) # TRANSPORTE + String(15)) # MODTRANS driver_name: Mapped[Optional[str]] = mapped_column(String(80)) # CONDUCTOR is_rail: Mapped[Optional[str]] = mapped_column(String(2)) # ESFERROCARRIL rail_id: Mapped[Optional[str]] = mapped_column( @@ -189,11 +210,13 @@ class InvoiceLogistics(Base, TenantScopedMixin, TimestampMixin): # --- 5. Sales Order Details (invoice_sales_details) --- class InvoiceSalesDetails(Base, TenantScopedMixin, TimestampMixin): __tablename__ = "invoice_sales_details" - __tableargs__ = {'schema': 'a76'} + __table_args__ = ( + {"schema": "a76"}, + ) - detail_id: Mapped[int] = mapped_column( + id: Mapped[int] = mapped_column( BigInteger, primary_key=True, autoincrement=True) - invoice_id: Mapped[int] = mapped_column(ForeignKey("invoice_header.id")) + invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id")) line_number: Mapped[int] = mapped_column(Integer) # LINEA sales_order: Mapped[Optional[str]] = mapped_column( @@ -214,11 +237,13 @@ class InvoiceSalesDetails(Base, TenantScopedMixin, TimestampMixin): # --- 6. Collections (invoice_collections) --- class InvoiceCollections(Base, TenantScopedMixin, TimestampMixin): __tablename__ = "invoice_collections" - __tableargs__ = {'schema': 'a76'} + __table_args__ = ( + {"schema": "a76"}, + ) - collection_id: Mapped[int] = mapped_column( + id: Mapped[int] = mapped_column( BigInteger, primary_key=True, autoincrement=True) - invoice_id: Mapped[int] = mapped_column(ForeignKey("invoice_header.id")) + invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id")) concept: Mapped[Optional[str]] = mapped_column(String(100)) # CONCEPTO is_collected: Mapped[Optional[int]] = mapped_column(Integer) # COBRADO diff --git a/backend/api/v1/modules/a76/invoices/schemas.py b/backend/api/v1/modules/a76/invoices/schemas.py index 70280e46..8075cd94 100644 --- a/backend/api/v1/modules/a76/invoices/schemas.py +++ b/backend/api/v1/modules/a76/invoices/schemas.py @@ -5,24 +5,35 @@ from pydantic import BaseModel, Field from .models import OperationType - # --- Base Schemas --- class InvoiceHeaderBase(BaseModel): """Base fields for Invoice Header""" - operation_type: Optional[OperationType] = Field(None, max_length=20, description="Operation type: imp/exp") - invoice_type: Optional[str] = Field(None, max_length=5, description="Invoice type key") - invoice_number: Optional[str] = Field(None, max_length=20, description="Invoice number") - project_number: Optional[str] = Field(None, max_length=14, description="Project number") - purchase_order: Optional[str] = Field(None, max_length=50, description="Purchase order") - related_doc_id: Optional[int] = Field(None, description="Related document ID for rectifications") + operation_type: Optional[OperationType] = Field( + None, max_length=20, description="Operation type: imp/exp") + invoice_type: Optional[str] = Field( + None, max_length=5, description="Invoice type key") + invoice_number: Optional[str] = Field( + None, max_length=20, description="Invoice number") + project_number: Optional[str] = Field( + None, max_length=14, description="Project number") + purchase_order: Optional[str] = Field( + None, max_length=50, description="Purchase order") + related_doc_id: Optional[int] = Field( + None, description="Related document ID for rectifications") invoice_date: Optional[date] = Field(None, description="Invoice date") - status: Optional[str] = Field(None, max_length=5, description="Status") - traffic_light_status: Optional[str] = Field(None, max_length=50, description="Traffic light status (SEMAFORO)") - process_log: Optional[str] = Field(None, max_length=300, description="Processing log") - comments: Optional[str] = Field(None, description="Comments and observations") - cfdi_uuid: Optional[str] = Field(None, max_length=100, description="CFDI UUID") - path_pdf: Optional[str] = Field(None, max_length=500, description="Path to PDF file") - path_xml: Optional[str] = Field(None, max_length=500, description="Path to XML file") + is_updated: Optional[bool] = Field(None, description="Status") + traffic_light_status: Optional[str] = Field( + None, max_length=50, description="Traffic light status (SEMAFORO)") + process_log: Optional[str] = Field( + None, max_length=300, description="Processing log") + comments_status: Optional[str] = Field( + None, description="Comments and observations") + cfdi_uuid: Optional[str] = Field( + None, max_length=100, description="CFDI UUID") + path_pdf: Optional[str] = Field( + None, max_length=500, description="Path to PDF file") + path_xml: Optional[str] = Field( + None, max_length=500, description="Path to XML file") class InvoiceComplianceMxBase(BaseModel): @@ -36,8 +47,8 @@ class InvoiceComplianceMxBase(BaseModel): None, max_length=5, description="Customs office") customs_agent: Optional[str] = Field( None, max_length=10, description="Customs agent") - is_mixed: Optional[str] = Field( - None, max_length=2, description="Is mixed operation") + is_mixed: Optional[bool] = Field( + None, description="Is mixed operation") waste_type: Optional[str] = Field( None, max_length=1, description="Waste type") appendix_17: Optional[int] = Field(None, description="Appendix 17") diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 2e0e9afd..86f3f4ce 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -72,6 +72,18 @@ "customs_sections": "Customs Sections", "anexo_22_app_31": "Anexo 22 App 3" }, + "import_invoices":{ + "title": "Import Invoices", + "temporary": "Temporary", + "definitive": "Definitive", + "mexican_purchases": "Mexican Purchases", + "regime_change": "Regime Change" + }, + "export_invoices": { + "title": "Export Invoices", + "exportation": "Exportation", + "repair": "Repair" + }, "clients_and_providers": "Clients and Providers", "customs_brokers": "Customs Brokers", "nav_user": { diff --git a/frontend/messages/es.json b/frontend/messages/es.json index ae2806e5..af9b2e69 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -72,6 +72,18 @@ "customs_sections": "Secciones Aduaneras", "anexo_22_app_31": "Anexo 22 App 3" }, + "import_invoices":{ + "title": "Facturas de importación", + "temporary": "Temporal", + "definitive": "Definitiva", + "mexican_purchases": "Compras mexicanas", + "regime_change": "Cambio de régimen" + }, + "export_invoices": { + "title": "Facturas de exportación", + "exportation": "Exportación", + "repair": "Reparación" + }, "clients_and_providers": "Clientes y Proveedores", "customs_brokers": "Agentes Aduanales", "nav_user": { diff --git a/frontend/src/lib/api/dashboard/a76/invoices.ts b/frontend/src/lib/api/dashboard/a76/invoices.ts new file mode 100644 index 00000000..99a23601 --- /dev/null +++ b/frontend/src/lib/api/dashboard/a76/invoices.ts @@ -0,0 +1,318 @@ +/** + * API Client para Facturas (Invoices) + * Gestiona las operaciones CRUD para facturas y sus relaciones + */ +import { api } from '$lib/api'; + +export type OperationType = 'imp' | 'exp'; +export type TransportType = 'none' | 'transport' | 'box' | 'licence plates' | 'truck' | 'vessel' | 'rail barge' | 'container' | 'airplane' | 'gondola' | 'flatbed'; + +// --- Interfaces --- + +export interface InvoiceComplianceMx { + invoice_id?: number; + pedimento?: string | null; + pedimento_code?: string | null; + remesa?: number | null; + aduana?: string | null; + provider_header?: string | null; + provider_id?: string | null; + sold_to_header?: string | null; + sold_to_id?: string | null; + shipped_to_header?: string | null; + shipped_to_id?: string | null; + shipped_by_header?: string | null; + shipped_by_id?: string | null; + customs_broker_id?: string | null; + is_mixed?: boolean | null; + waste_type?: string | null; + appendix_17?: number | null; + edocument?: string | null; + electronic_signature?: string | null; + sem_id?: number | null; +} + +export interface InvoiceFinancials { + id?: number; + invoice_id?: number; + currency?: string | null; + currency_type?: string | null; + exchange_rate?: number | null; + value_mn?: number | null; + value_me?: number | null; + customs_value_mn?: number | null; + freight?: number | null; + insurance?: number | null; + iva_mn?: number | null; + iva_factor?: number | null; + total_quantity?: number | null; + gross_weight?: number | null; + net_weight?: number | null; + bundle_count?: number | null; +} + +export interface InvoiceLogistics { + id?: number; + invoice_id?: number; + carrier_id?: string | null; + transport_type?: TransportType | null; + transport_mode?: string | null; + driver_name?: string | null; + is_rail?: string | null; + rail_id?: string | null; + vehicle_num?: string | null; + license_plate?: string | null; + seal_number?: string | null; + guide_number?: string | null; + entry_exit_date?: string | null; +} + +export interface InvoiceSalesDetails { + id?: number; + invoice_id?: number; + line_number: number; + sales_order?: string | null; + colors_description?: string | null; + square_color_code?: string | null; + line_bundles?: number | null; +} + +export interface InvoiceCollections { + id?: number; + invoice_id?: number; + concept?: string | null; + is_collected?: number | null; + collection_date?: string | null; + amount?: number | null; + collector_user?: string | null; +} + +export interface Invoice { + id: number; + tenant_id: number; + company_id: number; + operation_type?: OperationType | null; + invoice_type?: string | null; + invoice_number?: string | null; + project_number?: string | null; + purchase_order?: string | null; + related_doc_id?: number | null; + invoice_date?: string | null; + capture_date: string; + is_updated?: boolean | null; + updated_date?: string | null; + who_updated?: string | null; + traffic_light_status?: string | null; + process_log?: string | null; + observation_es?: string | null; + observation_en?: string | null; + comments_status?: string | null; + cfdi_uuid?: string | null; + path_pdf?: string | null; + path_xml?: string | null; + compliance_mx?: InvoiceComplianceMx | null; + financials?: InvoiceFinancials | null; + logistics?: InvoiceLogistics[]; + details?: InvoiceSalesDetails[]; + collections?: InvoiceCollections[]; +} + +export interface InvoiceListResponse { + items: Invoice[]; + total: number; + page: number; + page_size: number; +} + +export interface CreateInvoiceData { + operation_type?: OperationType | null; + invoice_type?: string | null; + invoice_number?: string | null; + project_number?: string | null; + purchase_order?: string | null; + related_doc_id?: number | null; + invoice_date?: string | null; + traffic_light_status?: string | null; + process_log?: string | null; + observation_es?: string | null; + observation_en?: string | null; + comments_status?: string | null; + cfdi_uuid?: string | null; + path_pdf?: string | null; + path_xml?: string | null; + compliance_mx?: Omit | null; + financials?: Omit | null; + logistics?: Omit[] | null; + details?: Omit[] | null; + collections?: Omit[] | null; +} + +export interface UpdateInvoiceData { + operation_type?: OperationType | null; + invoice_type?: string | null; + invoice_number?: string | null; + project_number?: string | null; + purchase_order?: string | null; + related_doc_id?: number | null; + invoice_date?: string | null; + traffic_light_status?: string | null; + process_log?: string | null; + observation_es?: string | null; + observation_en?: string | null; + comments_status?: string | null; + cfdi_uuid?: string | null; + path_pdf?: string | null; + path_xml?: string | null; + compliance_mx?: Partial | null; + financials?: Partial | null; + logistics?: Partial[] | null; + details?: Partial[] | null; + collections?: Partial[] | null; +} + +/** + * API para Facturas + */ +export const invoicesApi = { + /** + * Lista todas las facturas con paginación + */ + list: (companyId: number, page = 1, pageSize = 50, filters?: Record) => { + const params = new URLSearchParams({ + company_id: companyId.toString(), + page: page.toString(), + page_size: pageSize.toString() + }); + + // Agregar filtros si existen + if (filters) { + Object.entries(filters).forEach(([key, value]) => { + if (value !== null && value !== undefined && value !== '') { + params.append(key, String(value)); + } + }); + } + + return api.get(`/v1/a76/invoices?${params.toString()}`); + }, + + /** + * Obtiene una factura por ID + */ + get: (invoiceId: number, companyId: number) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.get(`/v1/a76/invoices/${invoiceId}?${params.toString()}`); + }, + + /** + * Crea una nueva factura + */ + create: (companyId: number, data: CreateInvoiceData) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.post(`/v1/a76/invoices?${params.toString()}`, data); + }, + + /** + * Actualiza una factura existente + */ + update: (invoiceId: number, companyId: number, data: UpdateInvoiceData) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.put(`/v1/a76/invoices/${invoiceId}?${params.toString()}`, data); + }, + + /** + * Elimina una factura + */ + delete: (invoiceId: number, companyId: number) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.delete(`/v1/a76/invoices/${invoiceId}?${params.toString()}`); + }, + + // --- Nested Resources --- + + /** + * Logística de factura + */ + logistics: { + list: (invoiceId: number, companyId: number) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.get(`/v1/a76/invoices/${invoiceId}/logistics?${params.toString()}`); + }, + + create: (invoiceId: number, companyId: number, data: Omit) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.post(`/v1/a76/invoices/${invoiceId}/logistics?${params.toString()}`, data); + }, + + delete: (invoiceId: number, logisticsId: number, companyId: number) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.delete(`/v1/a76/invoices/${invoiceId}/logistics/${logisticsId}?${params.toString()}`); + } + }, + + /** + * Detalles de venta de factura + */ + details: { + list: (invoiceId: number, companyId: number) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.get(`/v1/a76/invoices/${invoiceId}/details?${params.toString()}`); + }, + + create: (invoiceId: number, companyId: number, data: Omit) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.post(`/v1/a76/invoices/${invoiceId}/details?${params.toString()}`, data); + }, + + delete: (invoiceId: number, detailId: number, companyId: number) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.delete(`/v1/a76/invoices/${invoiceId}/details/${detailId}?${params.toString()}`); + } + }, + + /** + * Cobranzas de factura + */ + collections: { + list: (invoiceId: number, companyId: number) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.get(`/v1/a76/invoices/${invoiceId}/collections?${params.toString()}`); + }, + + create: (invoiceId: number, companyId: number, data: Omit) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.post(`/v1/a76/invoices/${invoiceId}/collections?${params.toString()}`, data); + }, + + delete: (invoiceId: number, collectionId: number, companyId: number) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.delete(`/v1/a76/invoices/${invoiceId}/collections/${collectionId}?${params.toString()}`); + } + } +}; diff --git a/frontend/src/lib/components/dashboard/invoices/columns.ts b/frontend/src/lib/components/dashboard/invoices/columns.ts new file mode 100644 index 00000000..d7f03fc5 --- /dev/null +++ b/frontend/src/lib/components/dashboard/invoices/columns.ts @@ -0,0 +1,89 @@ +/** + * Definición de columnas para la tabla de facturas + */ +import type { Invoice } from '$lib/api/dashboard/a76/invoices'; +import DataTableActions from './data-table-actions.svelte'; + +export function createColumns() { + return [ + { + accessorKey: 'id', + header: 'ID', + cell: (info: any) => info.getValue(), + enableSorting: true + }, + { + accessorKey: 'operation_type', + header: 'Tipo', + cell: (info: any) => { + const type = info.getValue(); + return type === 'imp' ? 'Importación' : type === 'exp' ? 'Exportación' : '-'; + } + }, + { + accessorKey: 'invoice_number', + header: 'Número de Factura', + cell: (info: any) => info.getValue() || '-' + }, + { + accessorKey: 'invoice_type', + header: 'Tipo Factura', + cell: (info: any) => info.getValue() || '-' + }, + { + accessorKey: 'project_number', + header: 'Proyecto', + cell: (info: any) => info.getValue() || '-' + }, + { + accessorKey: 'compliance_mx.pedimento', + header: 'Pedimento', + cell: (info: any) => { + const row = info.row.original; + return row.compliance_mx?.pedimento || '-'; + } + }, + { + accessorKey: 'invoice_date', + header: 'Fecha Factura', + cell: (info: any) => { + const date = info.getValue(); + if (!date) return '-'; + return new Date(date).toLocaleDateString('es-MX'); + } + }, + { + accessorKey: 'financials.value_mn', + header: 'Valor MN', + cell: (info: any) => { + const row = info.row.original; + const value = row.financials?.value_mn; + if (value === null || value === undefined) return '-'; + return new Intl.NumberFormat('es-MX', { + style: 'currency', + currency: 'MXN' + }).format(value); + } + }, + { + accessorKey: 'traffic_light_status', + header: 'Semáforo', + cell: (info: any) => info.getValue() || '-' + }, + { + accessorKey: 'capture_date', + header: 'Fecha Captura', + cell: (info: any) => { + const date = info.getValue(); + if (!date) return '-'; + return new Date(date).toLocaleDateString('es-MX'); + } + }, + { + id: 'actions', + header: 'Acciones', + cell: (info: any) => DataTableActions, + enableSorting: false + } + ]; +} diff --git a/frontend/src/lib/components/dashboard/invoices/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/create-edit-dialog.svelte new file mode 100644 index 00000000..6b523b0c --- /dev/null +++ b/frontend/src/lib/components/dashboard/invoices/create-edit-dialog.svelte @@ -0,0 +1,677 @@ + + + + + + + {isEditing ? "Editar Factura" : "Nueva Factura"} + + + {isEditing + ? "Modifica los datos de la factura" + : "Ingresa los datos de la nueva factura"} + + + +
+ + + General + Cumplimiento + Financieros + + + + +
+
+ + { + if (v) formData.operation_type = v as "imp" | "exp"; + }} + > + + {formData.operation_type === 'imp' ? 'Importación' : formData.operation_type === 'exp' ? 'Exportación' : 'Seleccionar tipo'} + + + Importación + Exportación + + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+
+ + + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ + + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+
+ + {#if error} +
+ {error} +
+ {/if} + + + + + +
+
+
diff --git a/frontend/src/lib/components/dashboard/invoices/data-table-actions.svelte b/frontend/src/lib/components/dashboard/invoices/data-table-actions.svelte new file mode 100644 index 00000000..13367343 --- /dev/null +++ b/frontend/src/lib/components/dashboard/invoices/data-table-actions.svelte @@ -0,0 +1,52 @@ + + + + + {#snippet child({ props })} + + {/snippet} + + + Acciones + + + + Ver Detalles + + + + Editar + + + + + Eliminar + + + diff --git a/frontend/src/lib/components/dashboard/invoices/data-table.svelte b/frontend/src/lib/components/dashboard/invoices/data-table.svelte new file mode 100644 index 00000000..14c41b7e --- /dev/null +++ b/frontend/src/lib/components/dashboard/invoices/data-table.svelte @@ -0,0 +1,131 @@ + + +
+
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {#if !header.isPlaceholder} + + {/if} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getVisibleCells() as cell (cell.id)} + + {#if cell.column.id === 'actions'} + {@const cellDef = cell.column.columnDef.cell} + {#if cellDef && typeof cellDef === 'function'} + {@const Component = cellDef(cell.getContext())} + + {/if} + {:else} + + {/if} + + {/each} + + {:else} + + + No hay resultados. + + + {/each} + + + {#if hasMore} + + +
+ {#if loading} +
+
+ Cargando más... +
+ {:else} +
+ Desplázate para cargar más +
+ {/if} +
+
+
+ {/if} +
+
+
+
diff --git a/frontend/src/lib/components/dashboard/invoices/delete-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/delete-dialog.svelte new file mode 100644 index 00000000..f33d7937 --- /dev/null +++ b/frontend/src/lib/components/dashboard/invoices/delete-dialog.svelte @@ -0,0 +1,110 @@ + + + + + + ¿Estás seguro? + +

Esta acción no se puede deshacer. Se eliminará permanentemente esta factura:

+ {#if item} +
+
+ ID: + {item.id} +
+
+ Número de Factura: + {item.invoice_number || 'N/A'} +
+
+ Tipo: + + {item.operation_type === 'imp' ? 'Importación' : + item.operation_type === 'exp' ? 'Exportación' : 'N/A'} + +
+
+ Proyecto: + {item.project_number || 'N/A'} +
+
+ Pedimento: + {item.compliance_mx?.pedimento || 'N/A'} +
+
+ {/if} + {#if error} +
+ {error} +
+ {/if} +
+
+ + Cancelar + + {#if loading} + + {/if} + Eliminar + + +
+
diff --git a/frontend/src/lib/components/dashboard/invoices/details-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/details-dialog.svelte new file mode 100644 index 00000000..e517faa3 --- /dev/null +++ b/frontend/src/lib/components/dashboard/invoices/details-dialog.svelte @@ -0,0 +1,435 @@ + + + (open = v)}> + + + Detalles de Factura #{invoice?.id} + + Información completa de la factura + + + + {#if invoice} + + + General + Cumplimiento + Financieros + Logística + Detalles + + + + +
+
+

Tipo de Operación

+

+ {#if invoice.operation_type === 'imp'} + Importación + {:else if invoice.operation_type === 'exp'} + Exportación + {:else} + - + {/if} +

+
+ +
+

Número de Factura

+

{invoice.invoice_number || '-'}

+
+ +
+

Tipo de Factura

+

{invoice.invoice_type || '-'}

+
+ +
+

Número de Proyecto

+

{invoice.project_number || '-'}

+
+ +
+

Orden de Compra

+

{invoice.purchase_order || '-'}

+
+ +
+

Fecha de Factura

+

{formatDate(invoice.invoice_date)}

+
+ +
+

Fecha de Captura

+

{formatDate(invoice.capture_date)}

+
+ +
+

Semáforo

+

{invoice.traffic_light_status || '-'}

+
+ +
+

CFDI UUID

+

{invoice.cfdi_uuid || '-'}

+
+

Actualizado

+

{invoice.is_updated ? 'Sí' : 'No'}

+
+ +
+

Observaciones (ES)

+

{invoice.observation_es || '-'}

+
+ +
+

Observaciones (EN)

+

{invoice.observation_en || '-'}

+
+ +
+

Log de Proceso

+

{invoice.process_log || '-'}

+
+
+
+ + + + {#if invoice.compliance_mx} +
+
+

Pedimento

+

{invoice.compliance_mx.pedimento || '-'}

+
+ +
+

Código de Pedimento

+

{invoice.compliance_mx.pedimento_code || '-'}

+
+ +
+

Remesa

+

{invoice.compliance_mx.remesa || '-'}

+
+ +
+

Aduana

+

{invoice.compliance_mx.aduana || '-'}

+
+ +
+

Agente Aduanal ID

+

{invoice.compliance_mx.customs_broker_id || '-'}

+
+ +
+

Proveedor

+

{invoice.compliance_mx.provider_id || '-'}

+
+ +
+

Vendido A

+

{invoice.compliance_mx.sold_to_id || '-'}

+
+ +
+

Enviado A

+

{invoice.compliance_mx.shipped_to_id || '-'}

+
+ +
+

Enviado Por

+

{invoice.compliance_mx.shipped_by_id || '-'}

+
+ +
+

Operación Mixta

+

{invoice.compliance_mx.is_mixed ? 'Sí' : 'No'}

+
+ +
+

Tipo de Desperdicio

+

{invoice.compliance_mx.waste_type || '-'}

+
+ +
+

Apéndice 17

+

{invoice.compliance_mx.appendix_17 || '-'}

+
+ +
+

E-Document

+

{invoice.compliance_mx.edocument || '-'}

+
+ +
+

Firma Electrónica

+

{invoice.compliance_mx.electronic_signature || '-'}

+
+
+ {:else} +

No hay información de cumplimiento disponible.

+ {/if} +
+ + + + {#if invoice.financials} +
+
+

Moneda

+

{invoice.financials.currency || '-'}

+
+ +
+

Tipo de Cambio

+

{formatNumber(invoice.financials.exchange_rate)}

+
+ +
+

Valor MN

+

{formatCurrency(invoice.financials.value_mn)}

+
+ +
+

Valor ME

+

{formatNumber(invoice.financials.value_me)}

+
+ +
+

Valor Aduana MN

+

{formatCurrency(invoice.financials.customs_value_mn)}

+
+ +
+

Flete

+

{formatCurrency(invoice.financials.freight)}

+
+ +
+

Seguro

+

{formatCurrency(invoice.financials.insurance)}

+
+ +
+

IVA MN

+

{formatCurrency(invoice.financials.iva_mn)}

+
+ +
+

Factor IVA

+

{formatNumber(invoice.financials.iva_factor)}

+
+ +
+

Cantidad Total

+

{formatNumber(invoice.financials.total_quantity)}

+
+ +
+

Peso Bruto

+

{formatNumber(invoice.financials.gross_weight)}

+
+ +
+

Peso Neto

+

{formatNumber(invoice.financials.net_weight)}

+
+ +
+

Número de Bultos

+

{invoice.financials.bundle_count || '-'}

+
+
+ {:else} +

No hay información financiera disponible.

+ {/if} +
+ + + + {#if invoice.logistics && invoice.logistics.length > 0} +
+ {#each invoice.logistics as logistics, index} +
+

Logística #{index + 1}

+
+
+

Transportista

+

{logistics.carrier_id || '-'}

+
+ +
+

Tipo de Transporte

+

{logistics.transport_type || '-'}

+
+ +
+

Modo de Transporte

+

{logistics.transport_mode || '-'}

+
+ +
+

Conductor

+

{logistics.driver_name || '-'}

+
+ +
+

Número de Vehículo

+

{logistics.vehicle_num || '-'}

+
+ +
+

Placa

+

{logistics.license_plate || '-'}

+
+ +
+

Número de Sello

+

{logistics.seal_number || '-'}

+
+ +
+

Guía

+

{logistics.guide_number || '-'}

+
+ +
+

Fecha Entrada/Salida

+

{formatDate(logistics.entry_exit_date)}

+
+
+
+ {/each} +
+ {:else} +

No hay información de logística disponible.

+ {/if} +
+ + + + {#if invoice.details && invoice.details.length > 0} +
+
+

Detalles de Venta

+
+ {#each invoice.details as detail} +
+
+
+

Línea

+

{detail.line_number}

+
+ +
+

Orden de Venta

+

{detail.sales_order || '-'}

+
+ +
+

Descripción de Colores

+

{detail.colors_description || '-'}

+
+ +
+

Código de Color

+

{detail.square_color_code || '-'}

+
+ +
+

Bultos

+

{detail.line_bundles || '-'}

+
+
+
+ {/each} +
+
+ + {#if invoice.collections && invoice.collections.length > 0} +
+

Cobranzas

+
+ {#each invoice.collections as collection} +
+
+
+

Concepto

+

{collection.concept || '-'}

+
+ +
+

Monto

+

{formatCurrency(collection.amount)}

+
+ +
+

Fecha de Cobranza

+

{formatDate(collection.collection_date)}

+
+ +
+

Cobrado

+

{collection.is_collected ? 'Sí' : 'No'}

+
+ +
+

Cobrador

+

{collection.collector_user || '-'}

+
+
+
+ {/each} +
+
+ {/if} +
+ {:else} +

No hay detalles de venta o cobranzas disponibles.

+ {/if} +
+
+ {/if} + + + + +
+
diff --git a/frontend/src/lib/components/sidebar/modules.ts b/frontend/src/lib/components/sidebar/modules.ts index 56c7e58d..60662981 100644 --- a/frontend/src/lib/components/sidebar/modules.ts +++ b/frontend/src/lib/components/sidebar/modules.ts @@ -1,4 +1,6 @@ import { + ArrowDownToLine, + ArrowUpFromLine, BadgeCheck, ChartPie, Database, @@ -314,6 +316,44 @@ export function getSidebarData(): SidebarData { }, ], }, + { + title: m["sidebar.import_invoices.title"](), + url: "#", + icon: ArrowDownToLine, + items: [ + { + title: m["sidebar.import_invoices.temporary"](), + url: "/dashboard/invoices", + }, + { + title: m["sidebar.import_invoices.definitive"](), + url: "/dashboard/invoices", + }, + { + title: m["sidebar.import_invoices.mexican_purchases"](), + url: "/dashboard/invoices", + }, + { + title: m["sidebar.import_invoices.regime_change"](), + url: "/dashboard/invoices", + } + ], + }, + { + title: m["sidebar.export_invoices.title"](), + url: "#", + icon: ArrowUpFromLine, + items: [ + { + title: m["sidebar.export_invoices.exportation"](), + url: "/dashboard/invoices", + }, + { + title: m["sidebar.export_invoices.repair"](), + url: "/dashboard/invoices", + }, + ], + }, { title: m["sidebar.clients_and_providers"](), url: "/dashboard/clients_and_providers", diff --git a/frontend/src/routes/dashboard/invoices/+page.server.ts b/frontend/src/routes/dashboard/invoices/+page.server.ts new file mode 100644 index 00000000..8b9ed8bb --- /dev/null +++ b/frontend/src/routes/dashboard/invoices/+page.server.ts @@ -0,0 +1,104 @@ +import type { PageServerLoad } from './$types'; +import { redirect } from '@sveltejs/kit'; +import { + getAuthTokens, + authenticatedFetch +} from '$lib/server/api'; + +export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => { + // Esperar a que el layout padre valide/refresque el token + const parentData = await parent(); + + // Verificar autenticación + const { accessToken } = getAuthTokens(cookies); + + if (!accessToken) { + throw redirect(302, '/login'); + } + + try { + // Obtener company_id de múltiples fuentes (en orden de prioridad): + // 1. URL query param (permite cambiar vía navegación) + // 2. Cookie active_company_id (setted por el team-switcher) + // 3. Primera compañía del usuario (fallback) + const companyIdParam = url.searchParams.get('company_id'); + const cookieCompanyId = cookies.get('active_company_id'); + + const companyId = companyIdParam + ? parseInt(companyIdParam) + : cookieCompanyId + ? parseInt(cookieCompanyId) + : parentData.companies?.[0]?.id; + + // Si aún no hay companyId, mostrar error + if (!companyId) { + return { + items: [], + total: 0, + page: 1, + page_size: 50, + error: 'No se encontró una compañía seleccionada', + companies: parentData.companies || [] + }; + } + + // Obtener filtro de tipo de operación + const operationType = url.searchParams.get('operation_type'); + + // Construir parámetros de consulta + const params = new URLSearchParams({ + company_id: companyId.toString(), + page: '1', + page_size: '50' + }); + + // Agregar filtro de tipo si existe y no es 'all' + if (operationType && operationType !== 'all') { + params.append('operation_type', operationType); + } + + // Usar authenticatedFetch para manejar automáticamente el refresh de tokens + const response = await authenticatedFetch( + `v1/a76/invoices?${params.toString()}`, + {}, + cookies, + fetch, + '/login' + ); + + if (!response.ok) { + return { + items: [], + total: 0, + page: 1, + page_size: 50, + error: 'Error al cargar facturas', + companies: parentData.companies || [], + currentCompanyId: companyId, + operationType: operationType || 'all' + }; + } + + const data = await response.json(); + + return { + items: data.items || [], + total: data.total || 0, + page: data.page || 1, + page_size: data.page_size || 50, + companies: parentData.companies || [], + currentCompanyId: companyId, + operationType: operationType || 'all' + }; + } catch (error) { + console.error('Error loading invoices:', error); + return { + items: [], + total: 0, + page: 1, + page_size: 50, + error: 'Error al cargar facturas', + companies: parentData.companies || [] + }; + } +}; diff --git a/frontend/src/routes/dashboard/invoices/+page.svelte b/frontend/src/routes/dashboard/invoices/+page.svelte new file mode 100644 index 00000000..c9fee531 --- /dev/null +++ b/frontend/src/routes/dashboard/invoices/+page.svelte @@ -0,0 +1,283 @@ + + +
+ +
+
+

Facturas

+

+ Gestiona las facturas de importación y exportación +

+
+ +
+ + + {#if error} + + + Error + {error} + + + {/if} + + + + +
+
+ Listado de Facturas + + Mostrando {allItems.length} de {totalItems} registros + {#if companyStore.activeCompany} + - Compañía: {companyStore.activeCompany.name} + {/if} + +
+
+ + + {selectedType === 'all' ? 'Todas' : selectedType === 'imp' ? 'Importación' : 'Exportación'} + + + Todas + Importación + Exportación + + + +
+
+
+ + + +
+
+ + + + + + +