From 2bd5d00827e61105a654cbcbcc40c44dc05dd3dc Mon Sep 17 00:00:00 2001
From: "edgar.mendez"
Date: Thu, 2 Oct 2025 16:16:12 -0600
Subject: [PATCH 1/5] =?UTF-8?q?Add:=20Administraci=C3=B3n=20de=20clasifica?=
=?UTF-8?q?ciones=20comerciales?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/layouts/AdminLayout.vue | 8 +
src/pages/ComercialClassifications/Create.vue | 89 +++++
src/pages/ComercialClassifications/Edit.vue | 139 +++++++
src/pages/ComercialClassifications/Form.vue | 68 ++++
src/pages/ComercialClassifications/Index.vue | 204 ++++++++++
.../ComercialClassifications/Modals/Show.vue | 362 ++++++++++++++++++
src/pages/ComercialClassifications/Module.js | 23 ++
.../comercial-classifications.interfaces.js | 62 +++
.../ComercialClassificationsService.js | 180 +++++++++
src/router/Index.js | 34 ++
10 files changed, 1169 insertions(+)
create mode 100644 src/pages/ComercialClassifications/Create.vue
create mode 100644 src/pages/ComercialClassifications/Edit.vue
create mode 100644 src/pages/ComercialClassifications/Form.vue
create mode 100644 src/pages/ComercialClassifications/Index.vue
create mode 100644 src/pages/ComercialClassifications/Modals/Show.vue
create mode 100644 src/pages/ComercialClassifications/Module.js
create mode 100644 src/pages/ComercialClassifications/interfaces/comercial-classifications.interfaces.js
create mode 100644 src/pages/ComercialClassifications/services/ComercialClassificationsService.js
diff --git a/src/layouts/AdminLayout.vue b/src/layouts/AdminLayout.vue
index 89cef95..2733699 100644
--- a/src/layouts/AdminLayout.vue
+++ b/src/layouts/AdminLayout.vue
@@ -94,6 +94,14 @@ onMounted(() => {
to="admin.units-measure.index"
/>
+
+
+import { onMounted, ref } from 'vue';
+import { useRouter, useRoute, RouterLink } from 'vue-router';
+import { api, useForm } from '@Services/Api';
+import { apiTo, transl, viewTo } from './Module';
+
+import IconButton from '@Holos/Button/Icon.vue'
+import PageHeader from '@Holos/PageHeader.vue';
+import GoogleIcon from '@Shared/GoogleIcon.vue';
+import Form from './Form.vue'
+
+/** Definidores */
+const router = useRouter();
+const route = useRoute();
+
+/** Propiedades */
+const form = useForm({
+ code: '',
+ name: '',
+ description: '',
+ parent_id: null
+});
+
+const parentInfo = ref(null);
+
+/** Métodos */
+function submit() {
+ form.transform(data => ({
+ ...data,
+ parent_id: data.parent_id // Usar el parent_id del formulario
+ })).post(apiTo('store'), {
+ onSuccess: () => {
+ Notify.success(Lang('register.create.onSuccess'))
+ router.push(viewTo({ name: 'index' }));
+ }
+ })
+}
+
+/** Ciclos */
+onMounted(() => {
+ // Verificar si se están pasando parámetros para crear subcategoría
+ if (route.query.parent_id) {
+ parentInfo.value = {
+ id: parseInt(route.query.parent_id),
+ name: route.query.parent_name,
+ code: route.query.parent_code
+ };
+ // Pre-llenar el parent_id en el formulario
+ form.parent_id = parseInt(route.query.parent_id);
+ }
+})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Creando subcategoría para:
+
+
+ {{ parentInfo.code }}
+ {{ parentInfo.name }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/pages/ComercialClassifications/Edit.vue b/src/pages/ComercialClassifications/Edit.vue
new file mode 100644
index 0000000..4527d2c
--- /dev/null
+++ b/src/pages/ComercialClassifications/Edit.vue
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Editando subcategoría de:
+
+
+ {{ form.parent.code }}
+ {{ form.parent.name }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/pages/ComercialClassifications/Form.vue b/src/pages/ComercialClassifications/Form.vue
new file mode 100644
index 0000000..bf02116
--- /dev/null
+++ b/src/pages/ComercialClassifications/Form.vue
@@ -0,0 +1,68 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/pages/ComercialClassifications/Index.vue b/src/pages/ComercialClassifications/Index.vue
new file mode 100644
index 0000000..459b9ba
--- /dev/null
+++ b/src/pages/ComercialClassifications/Index.vue
@@ -0,0 +1,204 @@
+
+
+
+
+
+
+
Clasificaciones Comerciales
+
+ Gestión de categorías y subcategorías de productos
+
+
+
+ Nueva Clasificación
+
+
+
+
+
searcher.pagination(page)">
+
+
+
+
+
+
+
+
+
+
+
+ └─
+
+ {{ model.code }}
+
+
+ Subcategoría
+
+
+
+
+ {{ model.name }}
+
+
+
+ {{ model.description || '-' }}
+
+
+
+
+ {{ model.is_active ? $t('active') : $t('inactive') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('registers.empty') }}
+
+
+
+ -
+ -
+ -
+ -
+ -
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/pages/ComercialClassifications/Modals/Show.vue b/src/pages/ComercialClassifications/Modals/Show.vue
new file mode 100644
index 0000000..1d84775
--- /dev/null
+++ b/src/pages/ComercialClassifications/Modals/Show.vue
@@ -0,0 +1,362 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('details') }}
+
+
+
+
+ {{ $t('code') }}:
+
+ {{ model.code }}
+
+
+
+ {{ $t('name') }}:
+ {{ model.name }}
+
+
+ {{ $t('description') }}:
+ {{ model.description }}
+
+
+
+
+ {{ $t('status') }}:
+
+ {{ model.is_active ? $t('Activo') : $t('Inactivo') }}
+
+
+
+ {{ $t('created_at') }}:
+ {{ getDateTime(model.created_at) }}
+
+
+ {{ $t('updated_at') }}:
+ {{ getDateTime(model.updated_at) }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('Subclasificaciones') }}
+
+
+
+
+
+
+ {{ child.code }}
+
+ {{ child.name }}
+
+
+ Eliminando...
+
+
+
+ {{ child.description }}
+
+
+
+
+
+
+
+ {{ child.is_active ? $t('Activo') : $t('Inactivo') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
No hay subcategorías
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/pages/ComercialClassifications/Module.js b/src/pages/ComercialClassifications/Module.js
new file mode 100644
index 0000000..24b9ccf
--- /dev/null
+++ b/src/pages/ComercialClassifications/Module.js
@@ -0,0 +1,23 @@
+import { lang } from '@Lang/i18n';
+import { hasPermission } from '@Plugins/RolePermission.js';
+
+// Ruta API
+const apiTo = (name, params = {}) => route(`comercial-classifications.${name}`, params)
+
+// Ruta visual
+const viewTo = ({ name = '', params = {}, query = {} }) => view({
+ name: `admin.comercial-classifications.${name}`, params, query
+})
+
+// Obtener traducción del componente
+const transl = (str) => lang(`admin.comercial_classifications.${str}`)
+
+// Control de permisos
+const can = (permission) => hasPermission(`admin.comercial-classifications.${permission}`)
+
+export {
+ can,
+ viewTo,
+ apiTo,
+ transl
+}
\ No newline at end of file
diff --git a/src/pages/ComercialClassifications/interfaces/comercial-classifications.interfaces.js b/src/pages/ComercialClassifications/interfaces/comercial-classifications.interfaces.js
new file mode 100644
index 0000000..8a68475
--- /dev/null
+++ b/src/pages/ComercialClassifications/interfaces/comercial-classifications.interfaces.js
@@ -0,0 +1,62 @@
+/**
+ * Interfaces para Comercial Classifications
+ *
+ * @author Sistema
+ * @version 1.0.0
+ */
+
+/**
+ * @typedef {Object} ComercialClassification
+ * @property {number} id - ID de la clasificación
+ * @property {string} code - Código de la clasificación
+ * @property {string} name - Nombre de la clasificación
+ * @property {string|null} description - Descripción de la clasificación
+ * @property {boolean} is_active - Estado activo/inactivo
+ * @property {number|null} parent_id - ID del padre
+ * @property {string} created_at - Fecha de creación
+ * @property {string} updated_at - Fecha de actualización
+ * @property {string|null} deleted_at - Fecha de eliminación
+ * @property {ComercialClassification|null} parent - Clasificación padre
+ * @property {ComercialClassification[]} children - Clasificaciones hijas
+ */
+
+/**
+ * @typedef {Object} ComercialClassificationResponse
+ * @property {string} status - Estado de la respuesta
+ * @property {Object} data - Datos de la respuesta
+ * @property {string} data.message - Mensaje de la respuesta
+ * @property {ComercialClassification} data.comercial_classification - Clasificación comercial
+ */
+
+/**
+ * @typedef {Object} ComercialClassificationsListResponse
+ * @property {string} status - Estado de la respuesta
+ * @property {Object} data - Datos de la respuesta
+ * @property {ComercialClassification[]} data.comercial_classifications - Lista de clasificaciones
+ */
+
+/**
+ * @typedef {Object} CreateComercialClassificationData
+ * @property {string} code - Código de la clasificación
+ * @property {string} name - Nombre de la clasificación
+ * @property {string|null} description - Descripción de la clasificación
+ * @property {boolean} is_active - Estado activo/inactivo
+ * @property {number|null} parent_id - ID del padre
+ */
+
+/**
+ * @typedef {Object} UpdateComercialClassificationData
+ * @property {string} [code] - Código de la clasificación
+ * @property {string} [name] - Nombre de la clasificación
+ * @property {string|null} [description] - Descripción de la clasificación
+ * @property {boolean} [is_active] - Estado activo/inactivo
+ * @property {number|null} [parent_id] - ID del padre
+ */
+
+export {
+ ComercialClassification,
+ ComercialClassificationResponse,
+ ComercialClassificationsListResponse,
+ CreateComercialClassificationData,
+ UpdateComercialClassificationData
+};
diff --git a/src/pages/ComercialClassifications/services/ComercialClassificationsService.js b/src/pages/ComercialClassifications/services/ComercialClassificationsService.js
new file mode 100644
index 0000000..129895b
--- /dev/null
+++ b/src/pages/ComercialClassifications/services/ComercialClassificationsService.js
@@ -0,0 +1,180 @@
+/**
+ * Servicio para Comercial Classifications
+ *
+ * @author Sistema
+ * @version 2.0.0
+ */
+
+import { api, apiURL } from '@Services/Api';
+
+export default class ComercialClassificationsService {
+
+ /**
+ * Obtener todas las clasificaciones comerciales
+ * @param {Object} params - Parámetros de la consulta
+ * @returns {Promise} Promesa con la respuesta
+ */
+ async getAll(params = {}) {
+ return new Promise((resolve, reject) => {
+ api.get(apiURL('comercial-classifications'), {
+ params,
+ onSuccess: (response) => resolve(response),
+ onError: (error) => reject(error)
+ });
+ });
+ }
+
+ /**
+ * Obtener una clasificación comercial por ID
+ * @param {number} id - ID de la clasificación
+ * @returns {Promise} Promesa con la respuesta
+ */
+ async getById(id) {
+ return new Promise((resolve, reject) => {
+ api.get(apiURL(`comercial-classifications/${id}`), {
+ onSuccess: (response) => resolve(response),
+ onError: (error) => reject(error)
+ });
+ });
+ }
+
+ /**
+ * Crear una nueva clasificación comercial
+ * @param {Object} data - Datos de la clasificación
+ * @param {string} data.code - Código de la clasificación
+ * @param {string} data.name - Nombre de la clasificación
+ * @param {string} data.description - Descripción de la clasificación
+ * @param {number|null} data.parent_id - ID de la clasificación padre (null para raíz)
+ * @param {boolean} data.is_active - Estado activo/inactivo
+ * @returns {Promise} Promesa con la respuesta
+ */
+ async create(data) {
+ return new Promise((resolve, reject) => {
+ api.post(apiURL('comercial-classifications'), {
+ data,
+ onSuccess: (response) => resolve(response),
+ onError: (error) => reject(error)
+ });
+ });
+ }
+
+ /**
+ * Actualizar una clasificación comercial existente
+ * @param {number} id - ID de la clasificación
+ * @param {Object} data - Datos actualizados
+ * @returns {Promise} Promesa con la respuesta
+ */
+ async update(id, data) {
+ return new Promise((resolve, reject) => {
+ api.put(apiURL(`comercial-classifications/${id}`), {
+ data,
+ onSuccess: (response) => resolve(response),
+ onError: (error) => reject(error)
+ });
+ });
+ }
+
+ /**
+ * Eliminar una clasificación comercial
+ * @param {number} id - ID de la clasificación
+ * @returns {Promise} Promesa con la respuesta
+ */
+ async delete(id) {
+ return new Promise((resolve, reject) => {
+ api.delete(apiURL(`comercial-classifications/${id}`), {
+ onSuccess: (response) => resolve(response),
+ onError: (error) => reject(error)
+ });
+ });
+ }
+
+ /**
+ * Actualizar solo el estado de una clasificación comercial
+ * @param {number} id - ID de la clasificación
+ * @param {boolean} is_active - Nuevo estado
+ * @returns {Promise} Promesa con la respuesta
+ */
+ async updateStatus(id, is_active) {
+ return new Promise((resolve, reject) => {
+ api.put(apiURL(`comercial-classifications/${id}`), {
+ data: { is_active },
+ onSuccess: (response) => {
+ resolve(response);
+ },
+ onError: (error) => {
+ // Mejorar el manejo de errores
+ const enhancedError = {
+ ...error,
+ timestamp: new Date().toISOString(),
+ action: 'updateStatus',
+ id: id,
+ is_active: is_active
+ };
+ reject(enhancedError);
+ }
+ });
+ });
+ }
+
+ /**
+ * Obtener clasificaciones padre disponibles (para selects)
+ * @param {number|null} excludeId - ID a excluir de la lista (para evitar loops)
+ * @returns {Promise} Promesa con la respuesta
+ */
+ async getParentOptions(excludeId = null) {
+ return new Promise((resolve, reject) => {
+ api.get(apiURL('comercial-classifications'), {
+ params: { exclude_children_of: excludeId },
+ onSuccess: (response) => {
+ // Aplanar la estructura jerárquica para el selector
+ const flattenOptions = (items, level = 0) => {
+ let options = [];
+ items.forEach(item => {
+ if (excludeId && (item.id === excludeId || this.hasDescendant(item, excludeId))) {
+ return; // Excluir item actual y sus descendientes
+ }
+
+ options.push({
+ ...item,
+ name: ' '.repeat(level) + item.name, // Indentación visual
+ level
+ });
+
+ if (item.children && item.children.length > 0) {
+ options = options.concat(flattenOptions(item.children, level + 1));
+ }
+ });
+ return options;
+ };
+
+ const flatOptions = flattenOptions(response.comercial_classifications?.data || response.data || []);
+ resolve(flatOptions);
+ },
+ onError: (error) => reject(error)
+ });
+ });
+ }
+
+ /**
+ * Función auxiliar para verificar si un item tiene como descendiente un ID específico
+ * @param {Object} item - Item a verificar
+ * @param {number} targetId - ID objetivo
+ * @returns {boolean} True si es descendiente
+ */
+ hasDescendant(item, targetId) {
+ if (!item.children) return false;
+ return item.children.some(child =>
+ child.id === targetId || this.hasDescendant(child, targetId)
+ );
+ }
+
+ /**
+ * Alternar el estado de una clasificación
+ * @param {Object} item - Objeto con la clasificación
+ * @returns {Promise} Promesa con la respuesta
+ */
+ async toggleStatus(item) {
+ const newStatus = !item.is_active;
+ return this.updateStatus(item.id, newStatus);
+ }
+}
\ No newline at end of file
diff --git a/src/router/Index.js b/src/router/Index.js
index 57db59a..5802ee6 100644
--- a/src/router/Index.js
+++ b/src/router/Index.js
@@ -494,6 +494,40 @@ const router = createRouter({
}
]
},
+ {
+ path: 'comercial-classifications',
+ name: 'admin.comercial-classifications',
+ meta: {
+ title: 'Clasificaciones Comerciales',
+ icon: 'category',
+ },
+ redirect: '/admin/comercial-classifications',
+ children: [
+ {
+ path: '',
+ name: 'admin.comercial-classifications.index',
+ component: () => import('@Pages/ComercialClassifications/Index.vue'),
+ },
+ {
+ path: 'create',
+ name: 'admin.comercial-classifications.create',
+ component: () => import('@Pages/ComercialClassifications/Create.vue'),
+ meta: {
+ title: 'Crear',
+ icon: 'add',
+ },
+ },
+ {
+ path: ':id/edit',
+ name: 'admin.comercial-classifications.edit',
+ component: () => import('@Pages/ComercialClassifications/Edit.vue'),
+ meta: {
+ title: 'Editar',
+ icon: 'edit',
+ },
+ }
+ ]
+ },
{
path: 'roles',
name: 'admin.roles',
--
2.45.2
From 83f0abff133f7007ce1ac9e0c423b40476843ca4 Mon Sep 17 00:00:00 2001
From: "edgar.mendez"
Date: Sat, 4 Oct 2025 09:23:04 -0600
Subject: [PATCH 2/5] ADD: Administrador de productos(WIP)
---
src/components/ui/Input.vue | 114 +++++++++++++
src/layouts/AdminLayout.vue | 5 +
src/pages/Products/Index.vue | 229 ++++++++++++++++++++++++++
src/pages/Products/Module.js | 23 +++
src/pages/Products/a.jsx | 300 +++++++++++++++++++++++++++++++++++
src/router/Index.js | 34 ++++
6 files changed, 705 insertions(+)
create mode 100644 src/components/ui/Input.vue
create mode 100644 src/pages/Products/Index.vue
create mode 100644 src/pages/Products/Module.js
create mode 100644 src/pages/Products/a.jsx
diff --git a/src/components/ui/Input.vue b/src/components/ui/Input.vue
new file mode 100644
index 0000000..1ca7f58
--- /dev/null
+++ b/src/components/ui/Input.vue
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/layouts/AdminLayout.vue b/src/layouts/AdminLayout.vue
index 2733699..3956bd5 100644
--- a/src/layouts/AdminLayout.vue
+++ b/src/layouts/AdminLayout.vue
@@ -100,6 +100,11 @@ onMounted(() => {
name="Clasificaciones Comerciales"
to="admin.comercial-classifications.index"
/>
+
diff --git a/src/pages/Products/Index.vue b/src/pages/Products/Index.vue
new file mode 100644
index 0000000..663d362
--- /dev/null
+++ b/src/pages/Products/Index.vue
@@ -0,0 +1,229 @@
+
+
+
+
+
+
+
Productos
+
+ Gestión del catálogo de productos
+
+
+
+
+ Nuevo Producto
+
+
+
+
+
+
+
+
+
+
+
+
150
+
Total Productos
+
+
+
+
+
+
+
+
+
+ Catálogo de Productos
+
+
+
+
+
+ searcher.search({ search: e.target.value })"
+ />
+
+
+
+ Filtros
+
+
+
+ searcher.pagination(page)"
+ >
+
+ {{ $t('code') }}
+ SKU
+ {{ $t('name') }}
+ {{ $t('description') }}
+ Atributos
+ Clasificaciones
+ {{ $t('status') }}
+ {{ $t('actions') }}
+
+
+
+
+
+
+
+
+ {{ product.code }}
+
+
+
+
+
+
+ {{ product.sku }}
+
+
+
+
+ {{ product.name }}
+
+
+
+
+ {{ product.description || '-' }}
+
+
+
+
+
+ {{ formatAttributes(product.attributes) }}
+
+
+
+
+
+
+ {{ classification.name }}
+
+
+ Sin clasificar
+
+
+
+
+
+
+ {{ product.is_active ? $t('active') : $t('inactive') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('registers.empty') }}
+
+
+ No se encontraron productos
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/pages/Products/Module.js b/src/pages/Products/Module.js
new file mode 100644
index 0000000..943a378
--- /dev/null
+++ b/src/pages/Products/Module.js
@@ -0,0 +1,23 @@
+import { lang } from '@Lang/i18n';
+import { hasPermission } from '@Plugins/RolePermission.js';
+
+// Ruta API
+const apiTo = (name, params = {}) => route(`products.${name}`, params)
+
+// Ruta visual
+const viewTo = ({ name = '', params = {}, query = {} }) => view({
+ name: `admin.products.${name}`, params, query
+})
+
+// Obtener traducción del componente
+const transl = (str) => lang(`admin.products.${str}`)
+
+// Control de permisos
+const can = (permission) => hasPermission(`admin.products.${permission}`)
+
+export {
+ can,
+ viewTo,
+ apiTo,
+ transl
+}
\ No newline at end of file
diff --git a/src/pages/Products/a.jsx b/src/pages/Products/a.jsx
new file mode 100644
index 0000000..4bbb939
--- /dev/null
+++ b/src/pages/Products/a.jsx
@@ -0,0 +1,300 @@
+import { useState } from "react"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Badge } from "@/components/ui/badge"
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table"
+import {
+ Plus,
+ Search,
+ Filter,
+ Edit,
+ Trash2,
+ Package,
+ MoreHorizontal
+} from "lucide-react"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+
+// Mock data
+const products = [
+ {
+ id: 1,
+ code: "PROD-001",
+ name: "Laptop Dell Inspiron 15",
+ description: "Laptop empresarial de alto rendimiento",
+ category: "Electrónicos > Computadoras",
+ brand: "Dell",
+ price: 15999.00,
+ stock: 45,
+ minStock: 10,
+ status: "active",
+ lastUpdated: "2024-01-15"
+ },
+ {
+ id: 2,
+ code: "PROD-002",
+ name: "Mouse Inalámbrico Logitech",
+ description: "Mouse óptico inalámbrico con receptor USB",
+ category: "Electrónicos > Accesorios",
+ brand: "Logitech",
+ price: 599.00,
+ stock: 120,
+ minStock: 25,
+ status: "active",
+ lastUpdated: "2024-01-14"
+ },
+ {
+ id: 3,
+ code: "PROD-003",
+ name: "Monitor LG 24 pulgadas",
+ description: "Monitor LED Full HD con conexión HDMI",
+ category: "Electrónicos > Monitores",
+ brand: "LG",
+ price: 3299.00,
+ stock: 8,
+ minStock: 15,
+ status: "low-stock",
+ lastUpdated: "2024-01-13"
+ },
+ {
+ id: 4,
+ code: "PROD-004",
+ name: "Teclado Mecánico RGB",
+ description: "Teclado mecánico para gaming con iluminación RGB",
+ category: "Electrónicos > Accesorios",
+ brand: "Corsair",
+ price: 2199.00,
+ stock: 0,
+ minStock: 5,
+ status: "out-of-stock",
+ lastUpdated: "2024-01-12"
+ },
+ {
+ id: 5,
+ code: "PROD-005",
+ name: "Webcam HD Logitech",
+ description: "Cámara web HD para videoconferencias",
+ category: "Electrónicos > Accesorios",
+ brand: "Logitech",
+ price: 899.00,
+ stock: 32,
+ minStock: 10,
+ status: "active",
+ lastUpdated: "2024-01-11"
+ }
+]
+
+const getStatusBadge = (status: string, stock: number, minStock: number) => {
+ if (stock === 0) {
+ return Sin Stock
+ }
+ if (stock <= minStock) {
+ return Stock Bajo
+ }
+ return Activo
+}
+
+export default function Products() {
+ const [searchTerm, setSearchTerm] = useState("")
+ const [filteredProducts, setFilteredProducts] = useState(products)
+
+ const handleSearch = (term: string) => {
+ setSearchTerm(term)
+ const filtered = products.filter(product =>
+ product.name.toLowerCase().includes(term.toLowerCase()) ||
+ product.code.toLowerCase().includes(term.toLowerCase()) ||
+ product.brand.toLowerCase().includes(term.toLowerCase())
+ )
+ setFilteredProducts(filtered)
+ }
+
+ return (
+
+ {/* Header */}
+
+
+
Productos
+
+ Gestión del catálogo de productos
+
+
+
+
+ Nuevo Producto
+
+
+
+ {/* Stats Cards */}
+
+
+
+
+
+
+
{products.length}
+
Total Productos
+
+
+
+
+
+
+
+
+ ✓
+
+
+
+ {products.filter(p => p.status === "active").length}
+
+
Activos
+
+
+
+
+
+
+
+
+ !
+
+
+
+ {products.filter(p => p.status === "low-stock").length}
+
+
Stock Bajo
+
+
+
+
+
+
+
+
+ ×
+
+
+
+ {products.filter(p => p.status === "out-of-stock").length}
+
+
Sin Stock
+
+
+
+
+
+
+ {/* Filters and Search */}
+
+
+ Catálogo de Productos
+
+
+
+
+
+ handleSearch(e.target.value)}
+ className="pl-10"
+ />
+
+
+
+ Filtros
+
+
+
+ {/* Products Table */}
+
+
+
+
+ Código
+ Producto
+ Categoría
+ Marca
+ Precio
+ Stock
+ Estado
+
+
+
+
+ {filteredProducts.map((product) => (
+
+
+ {product.code}
+
+
+
+
{product.name}
+
+ {product.description}
+
+
+
+
+ {product.category}
+
+ {product.brand}
+
+ ${product.price.toLocaleString('es-MX')}
+
+
+
+
{product.stock}
+
+ Min: {product.minStock}
+
+
+
+
+ {getStatusBadge(product.status, product.stock, product.minStock)}
+
+
+
+
+
+
+
+
+
+
+
+ Editar
+
+
+
+ Ver Stock
+
+
+
+ Eliminar
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/router/Index.js b/src/router/Index.js
index 5802ee6..cc5f7f2 100644
--- a/src/router/Index.js
+++ b/src/router/Index.js
@@ -528,6 +528,40 @@ const router = createRouter({
}
]
},
+ {
+ path: 'products',
+ name: 'admin.products',
+ meta: {
+ title: 'Productos',
+ icon: 'inventory',
+ },
+ redirect: '/admin/products',
+ children: [
+ {
+ path: '',
+ name: 'admin.products.index',
+ component: () => import('@Pages/Products/Index.vue'),
+ },
+ /* {
+ path: 'create',
+ name: 'admin.products.create',
+ component: () => import('@Pages/Products/Create.vue'),
+ meta: {
+ title: 'Crear',
+ icon: 'add',
+ },
+ },
+ {
+ path: ':id/edit',
+ name: 'admin.products.edit',
+ component: () => import('@Pages/Products/Edit.vue'),
+ meta: {
+ title: 'Editar',
+ icon: 'edit',
+ },
+ } */
+ ]
+ },
{
path: 'roles',
name: 'admin.roles',
--
2.45.2
From c222b66ceff1fe8ddc80b7880085259db71b4294 Mon Sep 17 00:00:00 2001
From: "edgar.mendez"
Date: Wed, 8 Oct 2025 12:45:48 -0600
Subject: [PATCH 3/5] Add: Punto de venta y UX para productos
---
package-lock.json | 7 +
package.json | 1 +
src/components/Holos/Button/Button.vue | 13 +-
src/components/ui/Icons/MaterialIcon.vue | 49 +
src/components/ui/Table/Table.vue | 117 +++
src/components/ui/Table/TableBody.vue | 56 ++
src/components/ui/Table/TableHeader.vue | 65 ++
src/components/ui/Table/TablePagination.vue | 148 +++
.../ui/Table/composables/usePagination.js | 60 ++
.../ui/Table/composables/useSort.js | 50 +
src/components/ui/Tags/Badge.vue | 109 +++
src/css/icons.css | 55 ++
src/lang/es.js | 15 +
src/layouts/AdminLayout.vue | 5 +
src/pages/Pos/Index.vue | 851 ++++++++++++++++++
src/pages/Pos/Module.js | 23 +
src/pages/Products/Create.vue | 54 ++
src/pages/Products/Edit.vue | 81 ++
src/pages/Products/Form.vue | 546 +++++++++++
src/pages/Products/Index.vue | 281 +++---
src/pages/Products/Modals/Show.vue | 342 +++++++
src/pages/Products/Module.js | 2 +
.../interfaces/products.interfaces.js | 82 ++
src/pages/Products/services/ProductService.js | 263 ++++++
src/router/Index.js | 23 +-
25 files changed, 3128 insertions(+), 170 deletions(-)
create mode 100644 src/components/ui/Icons/MaterialIcon.vue
create mode 100644 src/components/ui/Table/Table.vue
create mode 100644 src/components/ui/Table/TableBody.vue
create mode 100644 src/components/ui/Table/TableHeader.vue
create mode 100644 src/components/ui/Table/TablePagination.vue
create mode 100644 src/components/ui/Table/composables/usePagination.js
create mode 100644 src/components/ui/Table/composables/useSort.js
create mode 100644 src/components/ui/Tags/Badge.vue
create mode 100644 src/pages/Pos/Index.vue
create mode 100644 src/pages/Pos/Module.js
create mode 100644 src/pages/Products/Create.vue
create mode 100644 src/pages/Products/Edit.vue
create mode 100644 src/pages/Products/Form.vue
create mode 100644 src/pages/Products/Modals/Show.vue
create mode 100644 src/pages/Products/interfaces/products.interfaces.js
create mode 100644 src/pages/Products/services/ProductService.js
diff --git a/package-lock.json b/package-lock.json
index 6e1802a..4f8b8d0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,6 +17,7 @@
"axios": "^1.8.1",
"laravel-echo": "^2.0.2",
"luxon": "^3.5.0",
+ "material-symbols": "^0.36.2",
"pdf-lib": "^1.17.1",
"pinia": "^3.0.1",
"pusher-js": "^8.4.0",
@@ -2976,6 +2977,12 @@
"@jridgewell/sourcemap-codec": "^1.5.0"
}
},
+ "node_modules/material-symbols": {
+ "version": "0.36.2",
+ "resolved": "https://registry.npmjs.org/material-symbols/-/material-symbols-0.36.2.tgz",
+ "integrity": "sha512-FbxzGgQSmAb53Kajv+jyqcZ3Ck0ebfTBSMwHkMoyThsbrINiJb5mzheoiFXA/9MGc3cIl9XbhW8JxPM5vEP6iA==",
+ "license": "Apache-2.0"
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
diff --git a/package.json b/package.json
index cc69e1c..740790c 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"axios": "^1.8.1",
"laravel-echo": "^2.0.2",
"luxon": "^3.5.0",
+ "material-symbols": "^0.36.2",
"pdf-lib": "^1.17.1",
"pinia": "^3.0.1",
"pusher-js": "^8.4.0",
diff --git a/src/components/Holos/Button/Button.vue b/src/components/Holos/Button/Button.vue
index 362eba4..33bf969 100644
--- a/src/components/Holos/Button/Button.vue
+++ b/src/components/Holos/Button/Button.vue
@@ -23,7 +23,6 @@ interface Props {
loading?: boolean;
fullWidth?: boolean;
iconOnly?: boolean;
- asLink?: boolean; // Nueva prop para comportamiento de link
}
const props = withDefaults(defineProps(), {
@@ -35,7 +34,6 @@ const props = withDefaults(defineProps(), {
loading: false,
fullWidth: false,
iconOnly: false,
- asLink: true, // Por defecto no es link
});
@@ -44,15 +42,8 @@ const emit = defineEmits<{
}>();
function handleClick(event: MouseEvent) {
- // Si es usado como link, no bloquear la navegación
- if (props.asLink) {
- emit('click', event);
- return;
- }
-
- // Para botones normales, validar estados
if (props.disabled || props.loading) return;
-
+ emit('click', event);
}
const buttonClasses = computed(() => {
@@ -82,7 +73,7 @@ const buttonClasses = computed(() => {
solid: ['shadow-sm'],
outline: ['border', 'bg-white', 'hover:bg-gray-50'],
ghost: ['bg-transparent', 'hover:bg-gray-100'],
- smooth: ['bg-opacity-20', 'font-bold', 'shadow-none'],
+ smooth: ['bg-opacity-20', 'font-bold', 'uppercase', 'shadow-none'],
};
// Colores por tipo
diff --git a/src/components/ui/Icons/MaterialIcon.vue b/src/components/ui/Icons/MaterialIcon.vue
new file mode 100644
index 0000000..1dbbcad
--- /dev/null
+++ b/src/components/ui/Icons/MaterialIcon.vue
@@ -0,0 +1,49 @@
+
+
+ {{ name }}
+
+
+
+
+
+
diff --git a/src/components/ui/Table/Table.vue b/src/components/ui/Table/Table.vue
new file mode 100644
index 0000000..269620d
--- /dev/null
+++ b/src/components/ui/Table/Table.vue
@@ -0,0 +1,117 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/ui/Table/TableBody.vue b/src/components/ui/Table/TableBody.vue
new file mode 100644
index 0000000..9f95778
--- /dev/null
+++ b/src/components/ui/Table/TableBody.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
+ {{ formatCell(row[column.key], row, column) }}
+
+
+
+
+
+ {{ emptyMessage }}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/ui/Table/TableHeader.vue b/src/components/ui/Table/TableHeader.vue
new file mode 100644
index 0000000..b7625f8
--- /dev/null
+++ b/src/components/ui/Table/TableHeader.vue
@@ -0,0 +1,65 @@
+
+
+
+
+
+
{{ column.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/ui/Table/TablePagination.vue b/src/components/ui/Table/TablePagination.vue
new file mode 100644
index 0000000..b7a6d3a
--- /dev/null
+++ b/src/components/ui/Table/TablePagination.vue
@@ -0,0 +1,148 @@
+
+
+
+
+ Previous
+
+
+ Next
+
+
+
+
+
+ Mostrando
+ {{ startIndex + 1 }}
+ a
+ {{ endIndex }}
+ de
+ {{ totalItems }}
+ resultados
+
+
+
+
+
+ Previous
+
+
+
+
+
+
+ {{ page }}
+
+
+
+ Next
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/ui/Table/composables/usePagination.js b/src/components/ui/Table/composables/usePagination.js
new file mode 100644
index 0000000..9cc490c
--- /dev/null
+++ b/src/components/ui/Table/composables/usePagination.js
@@ -0,0 +1,60 @@
+import { computed, ref } from 'vue';
+
+export function usePagination(initialConfig) {
+ const currentPage = ref(initialConfig.currentPage);
+ const pageSize = ref(initialConfig.pageSize);
+ const totalItems = ref(initialConfig.totalItems);
+
+ const totalPages = computed(() => Math.ceil(totalItems.value / pageSize.value));
+
+ const startIndex = computed(() => (currentPage.value - 1) * pageSize.value);
+
+ const endIndex = computed(() => Math.min(startIndex.value + pageSize.value, totalItems.value));
+
+ const hasNextPage = computed(() => currentPage.value < totalPages.value);
+
+ const hasPreviousPage = computed(() => currentPage.value > 1);
+
+ const goToPage = (page) => {
+ if (page >= 1 && page <= totalPages.value) {
+ currentPage.value = page;
+ }
+ };
+
+ const nextPage = () => {
+ if (hasNextPage.value) {
+ currentPage.value++;
+ }
+ };
+
+ const previousPage = () => {
+ if (hasPreviousPage.value) {
+ currentPage.value--;
+ }
+ };
+
+ const setPageSize = (size) => {
+ pageSize.value = size;
+ currentPage.value = 1;
+ };
+
+ const updateTotalItems = (total) => {
+ totalItems.value = total;
+ };
+
+ return {
+ currentPage,
+ pageSize,
+ totalItems,
+ totalPages,
+ startIndex,
+ endIndex,
+ hasNextPage,
+ hasPreviousPage,
+ goToPage,
+ nextPage,
+ previousPage,
+ setPageSize,
+ updateTotalItems,
+ };
+}
diff --git a/src/components/ui/Table/composables/useSort.js b/src/components/ui/Table/composables/useSort.js
new file mode 100644
index 0000000..9a3e992
--- /dev/null
+++ b/src/components/ui/Table/composables/useSort.js
@@ -0,0 +1,50 @@
+import { ref, computed } from 'vue';
+
+export function useSort(initialData) {
+ const sortConfig = ref(null);
+
+ const sortedData = computed(() => {
+ if (!sortConfig.value) {
+ return initialData;
+ }
+
+ const { key, direction } = sortConfig.value;
+ const sorted = [...initialData];
+
+ sorted.sort((a, b) => {
+ const aValue = a[key];
+ const bValue = b[key];
+
+ if (aValue === bValue) return 0;
+
+ const comparison = aValue > bValue ? 1 : -1;
+ return direction === 'asc' ? comparison : -comparison;
+ });
+
+ return sorted;
+ });
+
+ const toggleSort = (key) => {
+ if (!sortConfig.value || sortConfig.value.key !== key) {
+ sortConfig.value = { key, direction: 'asc' };
+ } else if (sortConfig.value.direction === 'asc') {
+ sortConfig.value = { key, direction: 'desc' };
+ } else {
+ sortConfig.value = null;
+ }
+ };
+
+ const getSortDirection = (key) => {
+ if (!sortConfig.value || sortConfig.value.key !== key) {
+ return null;
+ }
+ return sortConfig.value.direction;
+ };
+
+ return {
+ sortConfig,
+ sortedData,
+ toggleSort,
+ getSortDirection,
+ };
+}
diff --git a/src/components/ui/Tags/Badge.vue b/src/components/ui/Tags/Badge.vue
new file mode 100644
index 0000000..cf4be8a
--- /dev/null
+++ b/src/components/ui/Tags/Badge.vue
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/css/icons.css b/src/css/icons.css
index e673e8f..d81e33f 100644
--- a/src/css/icons.css
+++ b/src/css/icons.css
@@ -75,3 +75,58 @@
font-weight: 400;
src: url(./icons/google/gNNBW2J8Roq16WD5tFNRaeLQk6-SHQ_R00k4c2_whPnoY9ruReYU3rHmz74m0ZkGH-VBYe1x0TV6x4yFH8F-H5OdzEL3sVTgJtfbYxOLojCL.woff2) format('woff2');
}
+
+/* Clases para MaterialIcon component */
+.material-symbols-outlined {
+ font-family: 'Material Symbols Outlined';
+ font-weight: normal;
+ font-style: normal;
+ font-size: 24px;
+ display: inline-block;
+ line-height: 1;
+ text-transform: none;
+ letter-spacing: normal;
+ word-wrap: normal;
+ white-space: nowrap;
+ direction: ltr;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ text-rendering: optimizeLegibility;
+ font-feature-settings: 'liga';
+}
+
+.material-symbols-rounded {
+ font-family: 'Material Symbols Rounded';
+ font-weight: normal;
+ font-style: normal;
+ font-size: 24px;
+ display: inline-block;
+ line-height: 1;
+ text-transform: none;
+ letter-spacing: normal;
+ word-wrap: normal;
+ white-space: nowrap;
+ direction: ltr;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ text-rendering: optimizeLegibility;
+ font-feature-settings: 'liga';
+}
+
+.material-symbols-sharp {
+ font-family: 'Material Symbols Sharp';
+ font-weight: normal;
+ font-style: normal;
+ font-size: 24px;
+ display: inline-block;
+ line-height: 1;
+ text-transform: none;
+ letter-spacing: normal;
+ word-wrap: normal;
+ white-space: nowrap;
+ direction: ltr;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ text-rendering: optimizeLegibility;
+ font-feature-settings: 'liga';
+}
diff --git a/src/lang/es.js b/src/lang/es.js
index 698d571..d410a72 100644
--- a/src/lang/es.js
+++ b/src/lang/es.js
@@ -78,6 +78,19 @@ export default {
activity: {
title: 'Historial de acciones',
description: 'Historial de acciones realizadas por los usuarios en orden cronológico.'
+ },
+ products: {
+ name: 'Productos',
+ title: 'Productos',
+ description: 'Gestión del catálogo de productos',
+ create: {
+ title: 'Crear producto',
+ description: 'Permite crear un nuevo producto en el catálogo con sus clasificaciones y atributos personalizados.'
+ },
+ edit: {
+ title: 'Editar producto',
+ description: 'Actualiza la información del producto, sus clasificaciones y atributos.'
+ }
}
},
app: {
@@ -193,6 +206,8 @@ export default {
done:'Hecho.',
edit:'Editar',
edited:'Registro creado',
+ active:'Activo',
+ inactive:'Inactivo',
email:{
title:'Correo',
verification:'Verificar correo'
diff --git a/src/layouts/AdminLayout.vue b/src/layouts/AdminLayout.vue
index 3956bd5..c7b5031 100644
--- a/src/layouts/AdminLayout.vue
+++ b/src/layouts/AdminLayout.vue
@@ -105,6 +105,11 @@ onMounted(() => {
name="Productos"
to="admin.products.index"
/>
+
diff --git a/src/pages/Pos/Index.vue b/src/pages/Pos/Index.vue
new file mode 100644
index 0000000..04691b4
--- /dev/null
+++ b/src/pages/Pos/Index.vue
@@ -0,0 +1,851 @@
+
+
+
+
+
+
+
Punto de Venta
+
Gestiona las ventas de productos
+
+
+
+
+ Egreso
+
+
+
+ Corte de Caja
+
+
+
+
+
+
+
+
+
+
+ Productos
+
+
+
+
+
+
Cargando productos...
+
+
+
+
+
+
No hay productos disponibles
+
+
+
+
+
+
+
+
+
+
{{ product.name }}
+
{{ product.sku }}
+
+
+
+
+ {{ product.category }}
+
+
+
+
+
+
+
${{ product.price.toFixed(2) }}
+
+
+ Stock: {{ product.stock }} unidades
+
+
+
+
+ Agregar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Carrito
+
+ {{ cartItemsCount }} productos
+
+
+
+
+
+
+
+
El carrito está vacío
+
+
+
+
+
+
+
+
+
+
+
{{ item.name }}
+
{{ item.sku }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.quantity }}
+
+
+
+
+
+
+ ${{ (item.price * item.quantity).toFixed(2) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Subtotal
+ ${{ subtotal.toFixed(2) }}
+
+
+ IVA (16%)
+ ${{ tax.toFixed(2) }}
+
+
+
+ Total
+ ${{ total.toFixed(2) }}
+
+
+
+
+
+
+
+ Procesar Venta
+
+
+ Limpiar Carrito
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Total a Pagar
+ ${{ total.toFixed(2) }}
+
+
+
+
+
+
+
+
+
+ Monto Recibido
+
+
+
+
+
+
+
+ Cambio
+
+ ${{ change.toFixed(2) }}
+
+
+
+
+
+
+
+
+
+
+ Confirmar Venta
+
+
+
+
+
+
+
+
+
+
Resumen de Ventas
+
+
+
+ Total Ventas
+ {{ totalSalesCount }}
+
+
+
+
+ Ventas Efectivo
+ ${{ totalCashSales.toFixed(2) }}
+
+
+
+
+ Ventas Tarjeta
+ ${{ totalCardSales.toFixed(2) }}
+
+
+
+
+ Total Egresos
+ ${{ totalExpenses.toFixed(2) }}
+
+
+
+
+
+ Efectivo Esperado en Caja
+ ${{ expectedCash.toFixed(2) }}
+
+ (Ventas en efectivo - Egresos)
+
+
+
+
+
+
+
+
Detalle de Ventas
+
+
+
+
+
{{ sale.id }}
+
+ {{ sale.timestamp.toLocaleTimeString() }} -
+ {{ sale.items.length }} items -
+ {{ sale.paymentMethod === 'cash' ? 'Efectivo' : 'Tarjeta' }}
+
+
+
${{ sale.total.toFixed(2) }}
+
+
+
+
+
+
+
+
Detalle de Egresos
+
+
+
+
+
{{ expense.description }}
+
+ {{ expense.timestamp.toLocaleTimeString() }}
+
+
+
-${{ expense.amount.toFixed(2) }}
+
+
+
+
+
+
+
+ Efectivo Contado
+
+
+
+
+
+
+
+
+ {{ cashDifference === 0 ? 'Cuadrado ✓' : cashDifference < 0 ? 'Faltante' : 'Sobrante' }}
+
+
+ ${{ Math.abs(cashDifference).toFixed(2) }}
+
+
+
+
+
+
+
+
+
+
+ Confirmar Corte
+
+
+
+
+
+
+
+
+
+
+
+ Descripción del Egreso
+
+
+
+ Describe brevemente el motivo del egreso
+
+
+
+
+
+
+ Monto
+
+
+
+ $
+
+
+
+
+ Ingresa el monto exacto del egreso
+
+
+
+
+
+
+
+
+
+
+ Información Importante
+
+
+ Los egresos se descontarán del efectivo esperado en el corte de caja.
+ Asegúrate de ingresar la información correcta.
+
+
+
+
+
+
+
+
+
Egresos Registrados Hoy
+
+
+
+
+
{{ expense.description }}
+
+ {{ expense.timestamp.toLocaleTimeString() }}
+
+
+
+ -${{ expense.amount.toFixed(2) }}
+
+
+
+
+ Total Egresos:
+ -${{ totalExpenses.toFixed(2) }}
+
+
+
+
+
+
+
+
+
+
+ Registrar Egreso
+
+
+
+
+
\ No newline at end of file
diff --git a/src/pages/Pos/Module.js b/src/pages/Pos/Module.js
new file mode 100644
index 0000000..943a378
--- /dev/null
+++ b/src/pages/Pos/Module.js
@@ -0,0 +1,23 @@
+import { lang } from '@Lang/i18n';
+import { hasPermission } from '@Plugins/RolePermission.js';
+
+// Ruta API
+const apiTo = (name, params = {}) => route(`products.${name}`, params)
+
+// Ruta visual
+const viewTo = ({ name = '', params = {}, query = {} }) => view({
+ name: `admin.products.${name}`, params, query
+})
+
+// Obtener traducción del componente
+const transl = (str) => lang(`admin.products.${str}`)
+
+// Control de permisos
+const can = (permission) => hasPermission(`admin.products.${permission}`)
+
+export {
+ can,
+ viewTo,
+ apiTo,
+ transl
+}
\ No newline at end of file
diff --git a/src/pages/Products/Create.vue b/src/pages/Products/Create.vue
new file mode 100644
index 0000000..85bdc08
--- /dev/null
+++ b/src/pages/Products/Create.vue
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/pages/Products/Edit.vue b/src/pages/Products/Edit.vue
new file mode 100644
index 0000000..0e17d5c
--- /dev/null
+++ b/src/pages/Products/Edit.vue
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/Products/Form.vue b/src/pages/Products/Form.vue
new file mode 100644
index 0000000..a3decb0
--- /dev/null
+++ b/src/pages/Products/Form.vue
@@ -0,0 +1,546 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/pages/Products/Index.vue b/src/pages/Products/Index.vue
index 663d362..9012552 100644
--- a/src/pages/Products/Index.vue
+++ b/src/pages/Products/Index.vue
@@ -1,61 +1,121 @@
@@ -67,27 +127,31 @@ onMounted(() => {
Gestión del catálogo de productos
-
-
- Nuevo Producto
-
-
+
+
+
+
+ Nuevo producto
+
+
+
-
-
+
-
150
+
3
Total Productos
-
@@ -96,133 +160,38 @@ onMounted(() => {
-
-
- searcher.search({ search: e.target.value })"
- />
-
-
-
- Filtros
-
+
Buscador
- searcher.pagination(page)"
- >
-
- {{ $t('code') }}
- SKU
- {{ $t('name') }}
- {{ $t('description') }}
- Atributos
- Clasificaciones
- {{ $t('status') }}
- {{ $t('actions') }}
+
+
+
+
+ {{ key }}
+
+
-
-
-
-
-
-
-
- {{ product.code }}
-
-
-
-
-
-
- {{ product.sku }}
-
-
-
-
- {{ product.name }}
-
-
-
-
- {{ product.description || '-' }}
-
-
-
-
-
- {{ formatAttributes(product.attributes) }}
-
-
-
-
-
-
- {{ classification.name }}
-
-
- Sin clasificar
-
-
-
-
-
-
- {{ product.is_active ? $t('active') : $t('inactive') }}
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ {{ value ? 'Activo' : 'Inactivo' }}
+
-
-
-
-
-
-
- {{ $t('registers.empty') }}
-
-
- No se encontraron productos
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
diff --git a/src/pages/Products/Modals/Show.vue b/src/pages/Products/Modals/Show.vue
new file mode 100644
index 0000000..b4b163b
--- /dev/null
+++ b/src/pages/Products/Modals/Show.vue
@@ -0,0 +1,342 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('details') }}
+
+
+
+
+ {{ $t('code') }}:
+
+ {{ model.code }}
+
+
+
+ SKU:
+
+ {{ model.sku }}
+
+
+
+ {{ $t('name') }}:
+ {{ model.name }}
+
+
+ {{ $t('description') }}:
+ {{ model.description }}
+
+
+
+
+ {{ $t('status') }}:
+
+ {{ model.is_active ? $t('Activo') : $t('Inactivo') }}
+
+
+
+ {{ $t('created_at') }}:
+ {{ getDateTime(model.created_at) }}
+
+
+ {{ $t('updated_at') }}:
+ {{ getDateTime(model.updated_at) }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('Atributos Personalizados') }}
+
+
+
+
+
+ {{ attr.key }}
+
+
+ {{ attr.value }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('Clasificaciones') }}
+
+
+
+
+
+ Clasificaciones de Almacén
+
+
+
+ {{ classification.code }}
+ {{ classification.name }}
+
+
+
+
+
+
+
+ Clasificaciones Comerciales
+
+
+
+ {{ classification.code }}
+ {{ classification.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('crud.edit') }}
+
+
+
+
+ {{ $t('Duplicar') }}
+
+
+
+
+
+
+
+
diff --git a/src/pages/Products/Module.js b/src/pages/Products/Module.js
index 943a378..d410ef1 100644
--- a/src/pages/Products/Module.js
+++ b/src/pages/Products/Module.js
@@ -3,6 +3,7 @@ import { hasPermission } from '@Plugins/RolePermission.js';
// Ruta API
const apiTo = (name, params = {}) => route(`products.${name}`, params)
+const comercialTo = (name, params = {}) => route(`comercial-classifications.${name}`, params)
// Ruta visual
const viewTo = ({ name = '', params = {}, query = {} }) => view({
@@ -19,5 +20,6 @@ export {
can,
viewTo,
apiTo,
+ comercialTo,
transl
}
\ No newline at end of file
diff --git a/src/pages/Products/interfaces/products.interfaces.js b/src/pages/Products/interfaces/products.interfaces.js
new file mode 100644
index 0000000..d8035c5
--- /dev/null
+++ b/src/pages/Products/interfaces/products.interfaces.js
@@ -0,0 +1,82 @@
+/**
+ * Interfaces para Products
+ *
+ * @author Sistema
+ * @version 1.0.0
+ */
+
+/**
+ * @typedef {Object} Product
+ * @property {number} id - ID del producto
+ * @property {string} code - Código del producto
+ * @property {string} sku - SKU del producto
+ * @property {string} name - Nombre del producto
+ * @property {string|null} description - Descripción del producto
+ * @property {Object|null} attributes - Atributos dinámicos del producto (JSON)
+ * @property {boolean} is_active - Estado activo/inactivo
+ * @property {string} created_at - Fecha de creación
+ * @property {string} updated_at - Fecha de actualización
+ * @property {string|null} deleted_at - Fecha de eliminación
+ * @property {ProductClassification[]} classifications - Clasificaciones asociadas
+ * @property {ProductClassification[]} warehouse_classifications - Clasificaciones de almacén
+ * @property {ProductClassification[]} comercial_classifications - Clasificaciones comerciales
+ */
+
+/**
+ * @typedef {Object} ProductClassification
+ * @property {number} id - ID de la clasificación
+ * @property {string} code - Código de la clasificación
+ * @property {string} name - Nombre de la clasificación
+ * @property {string} type - Tipo de clasificación (warehouse/comercial)
+ */
+
+/**
+ * @typedef {Object} ProductResponse
+ * @property {string} status - Estado de la respuesta
+ * @property {Object} data - Datos de la respuesta
+ * @property {string} data.message - Mensaje de la respuesta
+ * @property {Product} data.product - Producto
+ */
+
+/**
+ * @typedef {Object} ProductsListResponse
+ * @property {string} status - Estado de la respuesta
+ * @property {Object} data - Datos de la respuesta
+ * @property {Object} data.products - Lista de productos con paginación
+ * @property {Product[]} data.products.data - Array de productos
+ * @property {number} data.products.current_page - Página actual
+ * @property {number} data.products.total - Total de productos
+ */
+
+/**
+ * @typedef {Object} CreateProductData
+ * @property {string} code - Código del producto
+ * @property {string} sku - SKU del producto
+ * @property {string} name - Nombre del producto
+ * @property {string|null} description - Descripción del producto
+ * @property {Object|null} attributes - Atributos dinámicos (JSON)
+ * @property {boolean} is_active - Estado activo/inactivo
+ * @property {number[]} warehouse_classification_ids - IDs de clasificaciones de almacén
+ * @property {number[]} comercial_classification_ids - IDs de clasificaciones comerciales
+ */
+
+/**
+ * @typedef {Object} UpdateProductData
+ * @property {string} [code] - Código del producto
+ * @property {string} [sku] - SKU del producto
+ * @property {string} [name] - Nombre del producto
+ * @property {string|null} [description] - Descripción del producto
+ * @property {Object|null} [attributes] - Atributos dinámicos (JSON)
+ * @property {boolean} [is_active] - Estado activo/inactivo
+ * @property {number[]} [warehouse_classification_ids] - IDs de clasificaciones de almacén
+ * @property {number[]} [comercial_classification_ids] - IDs de clasificaciones comerciales
+ */
+
+export {
+ Product,
+ ProductClassification,
+ ProductResponse,
+ ProductsListResponse,
+ CreateProductData,
+ UpdateProductData
+};
diff --git a/src/pages/Products/services/ProductService.js b/src/pages/Products/services/ProductService.js
new file mode 100644
index 0000000..4fb8c82
--- /dev/null
+++ b/src/pages/Products/services/ProductService.js
@@ -0,0 +1,263 @@
+/**
+ * Servicio para Products
+ *
+ * @author Sistema
+ * @version 1.0.0
+ */
+
+import { api, apiURL } from '@Services/Api';
+
+export default class ProductService {
+
+ /**
+ * Obtener todos los productos
+ * @param {Object} params - Parámetros de la consulta
+ * @returns {Promise} Promesa con la respuesta
+ */
+ async getAll(params = {}) {
+ return new Promise((resolve, reject) => {
+ api.get(apiURL('catalogs/products'), {
+ params,
+ onSuccess: (response) => resolve(response),
+ onError: (error) => reject(error)
+ });
+ });
+ }
+
+ /**
+ * Obtener un producto por ID
+ * @param {number} id - ID del producto
+ * @returns {Promise} Promesa con la respuesta
+ */
+ async getById(id) {
+ return new Promise((resolve, reject) => {
+ api.get(apiURL(`catalogs/products/${id}`), {
+ onSuccess: (response) => resolve(response),
+ onError: (error) => reject(error)
+ });
+ });
+ }
+
+ /**
+ * Crear un nuevo producto
+ * @param {Object} data - Datos del producto
+ * @param {string} data.code - Código del producto
+ * @param {string} data.sku - SKU del producto
+ * @param {string} data.name - Nombre del producto
+ * @param {string|null} data.description - Descripción del producto
+ * @param {Object|null} data.attributes - Atributos dinámicos
+ * @param {boolean} data.is_active - Estado activo/inactivo
+ * @param {number[]} data.warehouse_classification_ids - IDs de clasificaciones de almacén
+ * @param {number[]} data.comercial_classification_ids - IDs de clasificaciones comerciales
+ * @returns {Promise} Promesa con la respuesta
+ */
+ async create(data) {
+ return new Promise((resolve, reject) => {
+ api.post(apiURL('catalogs/products'), {
+ data,
+ onSuccess: (response) => {
+ resolve(response);
+ },
+ onError: (error) => { reject(error); }
+ });
+ });
+ }
+
+ /**
+ * Actualizar un producto
+ * @param {number} id - ID del producto
+ * @param {Object} data - Datos a actualizar
+ * @param {string} [data.code] - Código del producto
+ * @param {string} [data.sku] - SKU del producto
+ * @param {string} [data.name] - Nombre del producto
+ * @param {string|null} [data.description] - Descripción del producto
+ * @param {Object|null} [data.attributes] - Atributos dinámicos
+ * @param {boolean} [data.is_active] - Estado activo/inactivo
+ * @param {number[]} [data.warehouse_classification_ids] - IDs de clasificaciones de almacén
+ * @param {number[]} [data.comercial_classification_ids] - IDs de clasificaciones comerciales
+ * @returns {Promise} Promesa con la respuesta
+ */
+ async update(id, data) {
+ return new Promise((resolve, reject) => {
+ api.put(apiURL(`catalogs/products/${id}`), {
+ data,
+ onSuccess: (response) => resolve(response),
+ onError: (error) => reject(error)
+ });
+ });
+ }
+
+ /**
+ * Actualizar solo el estado de un producto
+ * @param {number} id - ID del producto
+ * @param {boolean} is_active - Nuevo estado
+ * @returns {Promise} Promesa con la respuesta
+ */
+ async updateStatus(id, is_active) {
+ return new Promise((resolve, reject) => {
+ api.put(apiURL(`catalogs/products/${id}`), {
+ data: { is_active },
+ onSuccess: (response) => {
+ resolve(response);
+ },
+ onError: (error) => {
+ // Mejorar el manejo de errores
+ const enhancedError = {
+ ...error,
+ timestamp: new Date().toISOString(),
+ action: 'updateStatus',
+ id: id,
+ is_active: is_active
+ };
+ reject(enhancedError);
+ }
+ });
+ });
+ }
+
+ /**
+ * Eliminar un producto
+ * @param {number} id - ID del producto
+ * @returns {Promise} Promesa con la respuesta
+ */
+ async delete(id) {
+ return new Promise((resolve, reject) => {
+ api.delete(apiURL(`catalogs/products/${id}`), {
+ onSuccess: (response) => resolve(response),
+ onError: (error) => reject(error)
+ });
+ });
+ }
+
+ /**
+ * Alternar el estado de un producto
+ * @param {Object} item - Objeto con el producto
+ * @returns {Promise} Promesa con la respuesta
+ */
+ async toggleStatus(item) {
+ const newStatus = !item.is_active;
+ return this.updateStatus(item.id, newStatus);
+ }
+
+ /**
+ * Obtener clasificaciones disponibles para productos
+ * @param {string} type - Tipo de clasificación ('warehouse' o 'comercial')
+ * @returns {Promise} Promesa con la respuesta
+ */
+ async getClassifications(type = 'warehouse') {
+ return new Promise((resolve, reject) => {
+ const endpoint = type === 'warehouse'
+ ? 'catalogs/warehouse-classifications'
+ : 'comercial-classifications';
+
+ api.get(apiURL(endpoint), {
+ onSuccess: (response) => {
+ // Aplanar la estructura jerárquica para selects
+ const flattenOptions = (items, level = 0) => {
+ let options = [];
+ items.forEach(item => {
+ options.push({
+ ...item,
+ label: ' '.repeat(level) + item.name,
+ value: item.id,
+ level
+ });
+
+ if (item.children && item.children.length > 0) {
+ options = options.concat(flattenOptions(item.children, level + 1));
+ }
+ });
+ return options;
+ };
+
+ // Determinar la clave de datos según el tipo
+ const dataKey = type === 'warehouse'
+ ? 'warehouse_classifications'
+ : 'comercial_classifications';
+
+ const data = response[dataKey]?.data || response.data || [];
+ const flatOptions = flattenOptions(data);
+ resolve(flatOptions);
+ },
+ onError: (error) => reject(error)
+ });
+ });
+ }
+
+ /**
+ * Obtener clasificaciones de almacén
+ * @returns {Promise} Promesa con la respuesta
+ */
+ async getWarehouseClassifications() {
+ return this.getClassifications('warehouse');
+ }
+
+ /**
+ * Obtener clasificaciones comerciales
+ * @returns {Promise} Promesa con la respuesta
+ */
+ async getComercialClassifications() {
+ return this.getClassifications('comercial');
+ }
+
+ /**
+ * Buscar productos con filtros avanzados
+ * @param {Object} filters - Filtros de búsqueda
+ * @param {string} [filters.search] - Búsqueda por nombre, código o SKU
+ * @param {boolean} [filters.is_active] - Filtrar por estado
+ * @param {number[]} [filters.warehouse_classification_ids] - Filtrar por clasificaciones de almacén
+ * @param {number[]} [filters.comercial_classification_ids] - Filtrar por clasificaciones comerciales
+ * @returns {Promise} Promesa con la respuesta
+ */
+ async search(filters = {}) {
+ return this.getAll({ ...filters });
+ }
+
+ /**
+ * Exportar productos a formato específico
+ * @param {string} format - Formato de exportación (csv, excel, pdf)
+ * @param {Object} filters - Filtros aplicados
+ * @returns {Promise} Promesa con la respuesta
+ */
+ async export(format = 'excel', filters = {}) {
+ return new Promise((resolve, reject) => {
+ api.get(apiURL(`catalogs/products/export/${format}`), {
+ params: filters,
+ responseType: 'blob',
+ onSuccess: (response) => resolve(response),
+ onError: (error) => reject(error)
+ });
+ });
+ }
+
+ /**
+ * Duplicar un producto
+ * @param {number} id - ID del producto a duplicar
+ * @returns {Promise} Promesa con la respuesta
+ */
+ async duplicate(id) {
+ return new Promise((resolve, reject) => {
+ api.post(apiURL(`catalogs/products/${id}/duplicate`), {
+ onSuccess: (response) => resolve(response),
+ onError: (error) => reject(error)
+ });
+ });
+ }
+
+ /**
+ * Validar si un código o SKU ya existe
+ * @param {string} field - Campo a validar ('code' o 'sku')
+ * @param {string} value - Valor a validar
+ * @param {number|null} excludeId - ID a excluir de la validación (para edición)
+ * @returns {Promise} True si existe, false si no
+ */
+ async checkExists(field, value, excludeId = null) {
+ return new Promise((resolve, reject) => {
+ api.get(apiURL('catalogs/products/check-exists'), {
+ params: { field, value, exclude_id: excludeId },
+ onSuccess: (response) => resolve(response.exists || false),
+ onError: (error) => reject(error)
+ });
+ });
+ }
+}
diff --git a/src/router/Index.js b/src/router/Index.js
index cc5f7f2..a8fa196 100644
--- a/src/router/Index.js
+++ b/src/router/Index.js
@@ -542,15 +542,16 @@ const router = createRouter({
name: 'admin.products.index',
component: () => import('@Pages/Products/Index.vue'),
},
- /* {
+ {
path: 'create',
name: 'admin.products.create',
component: () => import('@Pages/Products/Create.vue'),
meta: {
title: 'Crear',
icon: 'add',
- },
- },
+ }
+ }
+ /*
{
path: ':id/edit',
name: 'admin.products.edit',
@@ -562,6 +563,22 @@ const router = createRouter({
} */
]
},
+ {
+ path: 'pos',
+ name: 'admin.pos',
+ meta: {
+ title: 'Punto de Venta',
+ icon: 'point_of_sale',
+ },
+ redirect: '/admin/pos',
+ children: [
+ {
+ path: '',
+ name: 'admin.pos.index',
+ component: () => import('@Pages/Pos/Index.vue'),
+ },
+ ]
+ },
{
path: 'roles',
name: 'admin.roles',
--
2.45.2
From 617c4a3a658edd9ec4f0dd5f129b69ff62bc09d6 Mon Sep 17 00:00:00 2001
From: "edgar.mendez"
Date: Wed, 15 Oct 2025 12:08:31 -0600
Subject: [PATCH 4/5] WIP
---
install.sh | 0
src/components/Holos/Inbox.vue | 0
src/components/Holos/Inbox/Item.vue | 0
src/components/Holos/Inbox/ItemTitle.vue | 0
src/components/Holos/Inbox/Menu/Item.vue | 0
src/components/Holos/Inbox/Menu/Static.vue | 0
src/controllers/PrintController.js | 0
src/pages/Warehouses/Details.vue | 126 +++++++++------------
8 files changed, 56 insertions(+), 70 deletions(-)
mode change 100755 => 100644 install.sh
mode change 100755 => 100644 src/components/Holos/Inbox.vue
mode change 100755 => 100644 src/components/Holos/Inbox/Item.vue
mode change 100755 => 100644 src/components/Holos/Inbox/ItemTitle.vue
mode change 100755 => 100644 src/components/Holos/Inbox/Menu/Item.vue
mode change 100755 => 100644 src/components/Holos/Inbox/Menu/Static.vue
mode change 100755 => 100644 src/controllers/PrintController.js
diff --git a/install.sh b/install.sh
old mode 100755
new mode 100644
diff --git a/src/components/Holos/Inbox.vue b/src/components/Holos/Inbox.vue
old mode 100755
new mode 100644
diff --git a/src/components/Holos/Inbox/Item.vue b/src/components/Holos/Inbox/Item.vue
old mode 100755
new mode 100644
diff --git a/src/components/Holos/Inbox/ItemTitle.vue b/src/components/Holos/Inbox/ItemTitle.vue
old mode 100755
new mode 100644
diff --git a/src/components/Holos/Inbox/Menu/Item.vue b/src/components/Holos/Inbox/Menu/Item.vue
old mode 100755
new mode 100644
diff --git a/src/components/Holos/Inbox/Menu/Static.vue b/src/components/Holos/Inbox/Menu/Static.vue
old mode 100755
new mode 100644
diff --git a/src/controllers/PrintController.js b/src/controllers/PrintController.js
old mode 100755
new mode 100644
diff --git a/src/pages/Warehouses/Details.vue b/src/pages/Warehouses/Details.vue
index 910e705..2215302 100644
--- a/src/pages/Warehouses/Details.vue
+++ b/src/pages/Warehouses/Details.vue
@@ -15,6 +15,7 @@ import IconButton from '@Holos/Button/Icon.vue';
import Button from '@Holos/Button/Button.vue';
import GoogleIcon from '@Shared/GoogleIcon.vue';
+import Badge from '@Components/ui/Tags/Badge.vue';
/** Definiciones */
const route = useRoute();
const router = useRouter();
@@ -247,12 +248,12 @@ onMounted(() => {
Ubicación
- {{ warehouse.address || 'Guadalajara, Zona Industrial' }}
+ {{ warehouse.address || 'Sin dirección' }}
-
+
-
+
Subclasificaciones
{
Estado
-
+
{{ warehouse.is_active ? 'Activo' : 'Inactivo' }}
-
+
@@ -323,6 +321,55 @@ onMounted(() => {
+
+
+
+
+
+ Stock por Productos
+
+
+ Inventario actual de productos en este almacén
+
+
+
+
+
+ {{ warehouseStock.warehouse }}
+
+
+
+
+
+
+
+ {{ product.code }}
+
+
+ {{ product.stock }} unidades
+
+
+
+
+ {{ product.name }}
+
+
+
+ Valor total:
+
+ {{ formatCurrency(product.value) }}
+
+
+
+
+
+
+
+
+
@@ -389,68 +436,7 @@ onMounted(() => {
-
-
-
-
-
- Stock por Productos
-
-
- Inventario actual de productos en este almacén
-
-
-
-
-
- {{ warehouseStock.warehouse }}
-
-
-
-
-
-
-
- {{ product.code }}
-
-
- {{ product.stock }} unidades
-
-
-
-
- {{ product.name }}
-
-
-
- Valor total:
-
- {{ formatCurrency(product.value) }}
-
-
-
-
-
-
- Stock
- {{ Math.round((product.stock / 100) * 100) }}%
-
-
-
-
-
-
-
-
+
--
2.45.2
From 7031389edcbe7eb07e4ac7fc720796cd0b69bf15 Mon Sep 17 00:00:00 2001
From: "edgar.mendez"
Date: Fri, 31 Oct 2025 00:27:17 -0600
Subject: [PATCH 5/5] Update Details and Index component on module Warehouses
---
package-lock.json | 86 ++++++++++++++++++++++++++++++++
package.json | 2 +
src/index.js | 56 ++++++++++++++++-----
src/pages/Warehouses/Details.vue | 1 +
src/pages/Warehouses/Index.vue | 20 ++++----
5 files changed, 142 insertions(+), 23 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 4f8b8d0..1111854 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "0.9.12",
"dependencies": {
"@popperjs/core": "^2.11.8",
+ "@primeuix/themes": "^1.2.5",
"@tailwindcss/postcss": "^4.0.9",
"@tailwindcss/vite": "^4.0.9",
"@vitejs/plugin-vue": "^5.2.1",
@@ -20,6 +21,7 @@
"material-symbols": "^0.36.2",
"pdf-lib": "^1.17.1",
"pinia": "^3.0.1",
+ "primevue": "^4.4.1",
"pusher-js": "^8.4.0",
"tailwindcss": "^4.0",
"toastr": "^2.1.4",
@@ -702,6 +704,74 @@
"url": "https://opencollective.com/popperjs"
}
},
+ "node_modules/@primeuix/styled": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/@primeuix/styled/-/styled-0.7.4.tgz",
+ "integrity": "sha512-QSO/NpOQg8e9BONWRBx9y8VGMCMYz0J/uKfNJEya/RGEu7ARx0oYW0ugI1N3/KB1AAvyGxzKBzGImbwg0KUiOQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@primeuix/utils": "^0.6.1"
+ },
+ "engines": {
+ "node": ">=12.11.0"
+ }
+ },
+ "node_modules/@primeuix/styles": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@primeuix/styles/-/styles-1.2.5.tgz",
+ "integrity": "sha512-nypFRct/oaaBZqP4jinT0puW8ZIfs4u+l/vqUFmJEPU332fl5ePj6DoOpQgTLzo3OfmvSmz5a5/5b4OJJmmi7Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@primeuix/styled": "^0.7.3"
+ }
+ },
+ "node_modules/@primeuix/themes": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@primeuix/themes/-/themes-1.2.5.tgz",
+ "integrity": "sha512-n3YkwJrHQaEESc/D/A/iD815sxp8cKnmzscA6a8Tm8YvMtYU32eCahwLLe6h5rywghVwxASWuG36XBgISYOIjQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@primeuix/styled": "^0.7.3"
+ }
+ },
+ "node_modules/@primeuix/utils": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/@primeuix/utils/-/utils-0.6.1.tgz",
+ "integrity": "sha512-tQL/ZOPgCdD+NTimlUmhyD0ey8J1XmpZE4hDHM+/fnuBicVVmlKOd5HpS748LcOVRUKbWjmEPdHX4hi5XZoC1Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.11.0"
+ }
+ },
+ "node_modules/@primevue/core": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/@primevue/core/-/core-4.4.1.tgz",
+ "integrity": "sha512-RG56iDKIJT//EtntjQzOiWOHZZJczw/qWWtdL5vFvw8/QDS9DPKn8HLpXK7N5Le6KK1MLXUsxoiGTZK+poUFUg==",
+ "license": "MIT",
+ "dependencies": {
+ "@primeuix/styled": "^0.7.4",
+ "@primeuix/utils": "^0.6.1"
+ },
+ "engines": {
+ "node": ">=12.11.0"
+ },
+ "peerDependencies": {
+ "vue": "^3.5.0"
+ }
+ },
+ "node_modules/@primevue/icons": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/@primevue/icons/-/icons-4.4.1.tgz",
+ "integrity": "sha512-UfDimrIjVdY6EziwieyV4zPKzW6mnKHKhy4Dgyjv2oI6pNeuim+onbJo1ce22PEGXW78vfblG/3/JIzVHFweqQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@primeuix/utils": "^0.6.1",
+ "@primevue/core": "4.4.1"
+ },
+ "engines": {
+ "node": ">=12.11.0"
+ }
+ },
"node_modules/@rollup/pluginutils": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
@@ -3315,6 +3385,22 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/primevue": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/primevue/-/primevue-4.4.1.tgz",
+ "integrity": "sha512-JbHBa5k30pZ7mn/z4vYBOnyt5GrR15eM3X0wa3VanonxnFLYkTEx8OMh33aU6ndWeOfi7Ef57dOL3bTH+3f4hQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@primeuix/styled": "^0.7.4",
+ "@primeuix/styles": "^1.2.5",
+ "@primeuix/utils": "^0.6.1",
+ "@primevue/core": "4.4.1",
+ "@primevue/icons": "4.4.1"
+ },
+ "engines": {
+ "node": ">=12.11.0"
+ }
+ },
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
diff --git a/package.json b/package.json
index 740790c..9bd1552 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
},
"dependencies": {
"@popperjs/core": "^2.11.8",
+ "@primeuix/themes": "^1.2.5",
"@tailwindcss/postcss": "^4.0.9",
"@tailwindcss/vite": "^4.0.9",
"@vitejs/plugin-vue": "^5.2.1",
@@ -22,6 +23,7 @@
"material-symbols": "^0.36.2",
"pdf-lib": "^1.17.1",
"pinia": "^3.0.1",
+ "primevue": "^4.4.1",
"pusher-js": "^8.4.0",
"tailwindcss": "^4.0",
"toastr": "^2.1.4",
diff --git a/src/index.js b/src/index.js
index c62b0fc..7944ce4 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,3 +1,4 @@
+import Aura from '@primeuix/themes/aura';
import './css/base.css'
import axios from 'axios';
@@ -13,10 +14,16 @@ import { pagePlugin } from '@Services/Page';
import { defineApp, reloadApp, view } from '@Services/Page';
import { apiURL } from '@Services/Api';
import VueApexCharts from "vue3-apexcharts";
-import VCalendar from 'v-calendar'
+import VCalendar from 'v-calendar';
+import PrimeVue from 'primevue/config';
import 'v-calendar/style.css';
-import App from '@Components/App.vue'
+import { definePreset } from '@primeuix/themes';
+import Button from 'primevue/button';
+
+
+
+import App from '@Components/App.vue'
import Error503 from '@Pages/Errors/503.vue'
import { hasToken } from './services/Api';
@@ -24,9 +31,9 @@ import { hasToken } from './services/Api';
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
// Elementos globales
-window.axios = axios;
-window.Lang = lang;
-window.Notify = new Notify();
+window.axios = axios;
+window.Lang = lang;
+window.Notify = new Notify();
window.TwScreen = new TailwindScreen();
async function boot() {
@@ -40,26 +47,44 @@ async function boot() {
window.Ziggy = routes.data;
defineApp(appData.data);
window.route = useRoute();
- window.view = view;
- initRoutes = true;
+ window.view = view;
+ initRoutes = true;
} catch (error) {
window.Notify.error(window.Lang('server.api.noAvailable'));
}
- if(initRoutes) {
+ if (initRoutes) {
// Iniciar permisos
- if(hasToken()) {
+ if (hasToken()) {
await bootPermissions();
await bootRoles();
-
+
// Iniciar broadcast
- if(import.meta.env.VITE_REVERB_ACTIVE === 'true') {
+ if (import.meta.env.VITE_REVERB_ACTIVE === 'true') {
await import('@Services/Broadcast')
}
}
-
+
reloadApp();
-
+
+ const MyPreset = definePreset(Aura, {
+ semantic: {
+ primary: {
+ 50: '{neutral.50}',
+ 100: '{neutral.100}',
+ 200: '{neutral.200}',
+ 300: '{neutral.300}',
+ 400: '{neutral.400}',
+ 500: '{neutral.500}',
+ 600: '{neutral.600}',
+ 700: '{neutral.700}',
+ 800: '{neutral.800}',
+ 900: '{neutral.900}',
+ 950: '{neutral.950}'
+ },
+ }
+ });
+
createApp(App)
.use(createPinia())
.use(i18n)
@@ -68,6 +93,11 @@ async function boot() {
.use(ZiggyVue)
.use(VCalendar, {})
.use(VueApexCharts)
+ .use(PrimeVue, {
+ theme: {
+ preset: MyPreset
+ }
+ })
.mount('#app');
} else {
createApp(Error503)
diff --git a/src/pages/Warehouses/Details.vue b/src/pages/Warehouses/Details.vue
index 2215302..981b96c 100644
--- a/src/pages/Warehouses/Details.vue
+++ b/src/pages/Warehouses/Details.vue
@@ -77,6 +77,7 @@ async function loadWarehouse() {
const response = await api.get(apiTo('show', { warehouse: route.params.id }), {
onSuccess: (r) => {
warehouse.value = r.warehouse;
+ console.log('Warehouse data:', r.warehouse);
},
onError: (err) => {
error.value = 'Error cargando el almacén';
diff --git a/src/pages/Warehouses/Index.vue b/src/pages/Warehouses/Index.vue
index 6b7a7f0..f4bfea2 100644
--- a/src/pages/Warehouses/Index.vue
+++ b/src/pages/Warehouses/Index.vue
@@ -16,7 +16,7 @@ import CardHeader from '@Holos/Card/CardHeader.vue';
import CardTitle from '@Holos/Card/CardTitle.vue';
import CardDescription from '@Holos/Card/CardDescription.vue';
import CardContent from '@Holos/Card/CardContent.vue';
-import Button from '@Holos/Button/Button.vue';
+//import Button from '@Holos/Button/Button.vue';
import GoogleIcon from '@Shared/GoogleIcon.vue';
@@ -58,6 +58,8 @@ const debugNavigation = (warehouse) => {
onMounted(() => {
searcher.search();
});
+
+import Button from "primevue/button"
@@ -67,6 +69,7 @@ onMounted(() => {
Inventario
Control de stock y movimientos por almacén
+
@@ -129,21 +132,18 @@ onMounted(() => {
-
- Ver Detalles
-
+
+
+
-
- Editar
-
+
-
- Eliminar
-
+
+
--
2.45.2