From 7c27200290b7868e31d460761e28061cc82b2380 Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Thu, 5 Feb 2026 12:00:54 -0600 Subject: [PATCH] =?UTF-8?q?feat:=20mejorar=20la=20gesti=C3=B3n=20de=20prod?= =?UTF-8?q?uctos=20estancados=20y=20agregar=20paginaci=C3=B3n=20en=20el=20?= =?UTF-8?q?servicio=20de=20reportes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Dashboard/Index.vue | 83 ++++++++++--------- src/pages/POS/Category/Index.vue | 4 +- src/pages/POS/Inventory/Index.vue | 133 ++++++++++++++++++++++++------ src/services/reportService.js | 8 +- 4 files changed, 159 insertions(+), 69 deletions(-) diff --git a/src/pages/Dashboard/Index.vue b/src/pages/Dashboard/Index.vue index 97441dc..c145046 100644 --- a/src/pages/Dashboard/Index.vue +++ b/src/pages/Dashboard/Index.vue @@ -6,11 +6,12 @@ import reportService from '@Services/reportService'; import useCashRegister from '@Stores/cashRegister'; import GoogleIcon from '@Shared/GoogleIcon.vue'; import OpenModal from '@/pages/POS/CashRegister/OpenModal.vue'; +import Table from '@Holos/Table.vue'; // State const router = useRouter(); const topProduct = ref(null); -const stagnantProducts = ref([]); +const stagnantProducts = ref({ data: [], total: 0 }); const loadingTopProduct = ref(true); const loadingStagnantProducts = ref(true); const today = new Date(); @@ -43,7 +44,7 @@ const fetchTopProduct = async () => { } }; -const fetchStagnantProducts = async () => { +const fetchStagnantProducts = async (page = 1) => { if (!filters.value.from_date || !filters.value.to_date) { window.Notify.warning('Por favor selecciona ambas fechas para consultar el reporte.'); return; @@ -54,11 +55,13 @@ const fetchStagnantProducts = async () => { const data = await reportService.getProductsWithoutMovement( filters.value.from_date, filters.value.to_date, - true + true, + page ); - stagnantProducts.value = data.products; + stagnantProducts.value = data.products || { data: [], total: 0 }; } catch (error) { window.Notify.error('Error al cargar productos sin movimiento.'); + stagnantProducts.value = { data: [], total: 0 }; } finally { loadingStagnantProducts.value = false; } @@ -191,43 +194,43 @@ onMounted(() => {

Cargando inventario...

- -
- -

¡Excelente rotación!

-

Todos los productos se han vendido en el período reciente.

-
- -
- - - - - - - - - - - - - - - -
ProductoStockValor Inventario
-

{{ product.name }}

-

{{ product.sku }}

-
- - {{ product.stock }} - - - - {{ formatCurrency(product.inventory_value) }} - -
-
+ + + + +
diff --git a/src/pages/POS/Category/Index.vue b/src/pages/POS/Category/Index.vue index 4489f9a..23702ad 100644 --- a/src/pages/POS/Category/Index.vue +++ b/src/pages/POS/Category/Index.vue @@ -21,9 +21,9 @@ const deletingCategory = ref(null); const searcher = useSearcher({ url: apiURL('categorias'), onSuccess: (r) => { - models.value = r.categories || []; + models.value = r.categories || { data: [], total: 0 }; }, - onError: () => models.value = [] + onError: () => models.value = { data: [], total: 0 } }); const openCreateModal = () => { diff --git a/src/pages/POS/Inventory/Index.vue b/src/pages/POS/Inventory/Index.vue index e30f890..ecc4ed2 100644 --- a/src/pages/POS/Inventory/Index.vue +++ b/src/pages/POS/Inventory/Index.vue @@ -18,6 +18,9 @@ import ImportModal from './ImportModal.vue'; /** Estado */ const models = ref([]); +const totalInventoryValue = ref(0); +const categories = ref([]); +const selectedCategory = ref(''); const showCreateModal = ref(false); const showEditModal = ref(false); const showDeleteModal = ref(false); @@ -31,12 +34,31 @@ const searcher = useSearcher({ url: apiURL('inventario'), onSuccess: (r) => { models.value = r.products || { data: [], total: 0 }; + totalInventoryValue.value = r.total_inventory_value || 0; }, onError: () => { models.value = { data: [], total: 0 }; + totalInventoryValue.value = 0; } }); +const loadCategories = async () => { + try { + const response = await fetch(apiURL('categorias'), { + headers: { + 'Authorization': `Bearer ${sessionStorage.token}`, + 'Accept': 'application/json' + } + }); + const result = await response.json(); + if (result.data && result.data.categories && result.data.categories.data) { + categories.value = result.data.categories.data; + } + } catch (error) { + console.error('Error al cargar categorías:', error); + } +}; + const openCreateModal = () => { showCreateModal.value = true; }; @@ -74,11 +96,15 @@ const closeImportModal = () => { }; const onProductSaved = () => { - searcher.search(); + searcher.search('', { + category_id: selectedCategory.value || '' + }); }; const onProductsImported = () => { - searcher.search(); + searcher.search('', { + category_id: selectedCategory.value || '' + }); }; const openSerials = (product) => { @@ -98,7 +124,9 @@ const confirmDelete = async (id) => { if (response.ok) { Notify.success('Producto eliminado exitosamente'); closeDeleteModal(); - searcher.search(); + searcher.search('', { + category_id: selectedCategory.value || '' + }); } else { Notify.error('Error al eliminar el producto'); } @@ -108,13 +136,18 @@ const confirmDelete = async (id) => { } }; +const handleCategoryChange = () => { + searcher.search('', { + category_id: selectedCategory.value || '' + }); +}; + const exportReport = async () => { try { isExporting.value = true; - // Puedes agregar filtros aquí si lo necesitas const filters = { - // category_id: 5, // Opcional: filtrar por categoría específica + category_id: selectedCategory.value || null, // with_serials_only: true, // Opcional: solo productos con seguimiento de seriales // low_stock_threshold: 10 // Opcional: solo productos con stock bajo o igual al umbral }; @@ -132,7 +165,10 @@ const exportReport = async () => { /** Ciclos */ onMounted(() => { - searcher.search(); + loadCategories(); + searcher.search('', { + category_id: selectedCategory.value || '' + }); }); @@ -141,7 +177,7 @@ onMounted(() => { + +
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+

Valor Total del Inventario

+

+ {{ formatCurrency(totalInventoryValue) }} +

+
+
+
+

Total de Productos

+

{{ models.total || 0 }}

+
+
+
+
+
{ @send-pagination="(page) => searcher.pagination(page)" >