diff --git a/src/components/layout/Sidebar.vue b/src/components/layout/Sidebar.vue
new file mode 100644
index 0000000..6e2c16b
--- /dev/null
+++ b/src/components/layout/Sidebar.vue
@@ -0,0 +1,118 @@
+
+
+
+
+
diff --git a/src/components/Holos/AppTopbar.vue b/src/components/layout/TopBar.vue
similarity index 98%
rename from src/components/Holos/AppTopbar.vue
rename to src/components/layout/TopBar.vue
index 1db2131..af6c9f6 100644
--- a/src/components/Holos/AppTopbar.vue
+++ b/src/components/layout/TopBar.vue
@@ -10,7 +10,7 @@ const { isDarkMode, toggleDarkMode } = useLayout();
- Mi Aplicación
+ GOLS Control
diff --git a/src/components/shared/KpiCard.vue b/src/components/shared/KpiCard.vue
new file mode 100644
index 0000000..72a1b23
--- /dev/null
+++ b/src/components/shared/KpiCard.vue
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+ {{ title }}
+
+
+ {{ value }}
+
+
+
+
+
+
+ {{ Math.abs(trend.value) }}%
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/warehouse/components/InventoryTable.vue b/src/modules/warehouse/components/InventoryTable.vue
new file mode 100644
index 0000000..647b1bf
--- /dev/null
+++ b/src/modules/warehouse/components/InventoryTable.vue
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ data.sku }}
+
+
+
+
+
+
+
{{ data.name }}
+
{{ data.category }}
+
+
+
+
+
+
+
+
+
+ {{ data.quantity }}
+
+
+
+
+
+
+
+ {{ data.minStock }}
+
+
+
+
+
+ {{ formatCurrency(data.unitPrice) }}
+
+
+
+
+
+ {{ formatDate(data.lastUpdated) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/warehouse/components/WarehouseDashboard.vue b/src/modules/warehouse/components/WarehouseDashboard.vue
new file mode 100644
index 0000000..454bc07
--- /dev/null
+++ b/src/modules/warehouse/components/WarehouseDashboard.vue
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+ Dashboard de Almacén
+
+
+ Visión general del inventario y movimientos
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/warehouse/composables/useWarehouse.ts b/src/modules/warehouse/composables/useWarehouse.ts
new file mode 100644
index 0000000..41a44db
--- /dev/null
+++ b/src/modules/warehouse/composables/useWarehouse.ts
@@ -0,0 +1,82 @@
+import { ref, computed } from 'vue';
+import type { Product, WarehouseStats } from '../types/warehouse';
+import { warehouseService } from '../services/warehouseService';
+
+export function useWarehouse() {
+ const products = ref
([]);
+ const stats = ref(null);
+ const loading = ref(false);
+ const error = ref(null);
+
+ // Computed properties
+ const lowStockProducts = computed(() =>
+ products.value.filter(p => p.quantity < p.minStock)
+ );
+
+ const totalInventoryValue = computed(() =>
+ products.value.reduce((sum, p) => sum + (p.quantity * p.unitPrice), 0)
+ );
+
+ // Methods
+ const loadProducts = async () => {
+ loading.value = true;
+ error.value = null;
+ try {
+ products.value = await warehouseService.getProducts();
+ } catch (err) {
+ error.value = 'Error al cargar productos';
+ console.error(err);
+ } finally {
+ loading.value = false;
+ }
+ };
+
+ const loadStats = async () => {
+ loading.value = true;
+ error.value = null;
+ try {
+ stats.value = await warehouseService.getStats();
+ } catch (err) {
+ error.value = 'Error al cargar estadísticas';
+ console.error(err);
+ } finally {
+ loading.value = false;
+ }
+ };
+
+ const updateQuantity = async (productId: string, quantity: number) => {
+ loading.value = true;
+ error.value = null;
+ try {
+ const updated = await warehouseService.updateProductQuantity(productId, quantity);
+ if (updated) {
+ const index = products.value.findIndex(p => p.id === productId);
+ if (index !== -1) {
+ products.value[index] = updated;
+ }
+ }
+ } catch (err) {
+ error.value = 'Error al actualizar cantidad';
+ console.error(err);
+ } finally {
+ loading.value = false;
+ }
+ };
+
+ return {
+ // State
+ products,
+ stats,
+ loading,
+ error,
+
+ // Computed
+ lowStockProducts,
+ totalInventoryValue,
+
+ // Methods
+ loadProducts,
+ loadStats,
+ updateQuantity
+ };
+}
diff --git a/src/modules/warehouse/services/warehouseService.ts b/src/modules/warehouse/services/warehouseService.ts
new file mode 100644
index 0000000..d1d79a0
--- /dev/null
+++ b/src/modules/warehouse/services/warehouseService.ts
@@ -0,0 +1,152 @@
+import type { Product, WarehouseMovement, WarehouseStats } from '../types/warehouse';
+
+// Simulación de datos de ejemplo
+const mockProducts: Product[] = [
+ {
+ id: '1',
+ name: 'Laptop Dell XPS 15',
+ sku: 'LAP-001',
+ category: 'Electrónica',
+ quantity: 45,
+ minStock: 10,
+ maxStock: 100,
+ unitPrice: 1299.99,
+ location: 'A-1-01',
+ lastUpdated: new Date('2025-11-01')
+ },
+ {
+ id: '2',
+ name: 'Mouse Logitech MX Master',
+ sku: 'MOU-001',
+ category: 'Accesorios',
+ quantity: 8,
+ minStock: 15,
+ maxStock: 50,
+ unitPrice: 99.99,
+ location: 'B-2-03',
+ lastUpdated: new Date('2025-11-03')
+ },
+ {
+ id: '3',
+ name: 'Teclado Mecánico RGB',
+ sku: 'KEY-001',
+ category: 'Accesorios',
+ quantity: 120,
+ minStock: 20,
+ maxStock: 150,
+ unitPrice: 149.99,
+ location: 'B-2-05',
+ lastUpdated: new Date('2025-11-04')
+ }
+];
+
+const mockMovements: WarehouseMovement[] = [
+ {
+ id: '1',
+ productId: '1',
+ productName: 'Laptop Dell XPS 15',
+ type: 'in',
+ quantity: 20,
+ reason: 'Compra a proveedor',
+ date: new Date('2025-11-01'),
+ user: 'Admin'
+ },
+ {
+ id: '2',
+ productId: '2',
+ productName: 'Mouse Logitech MX Master',
+ type: 'out',
+ quantity: 5,
+ reason: 'Venta',
+ date: new Date('2025-11-03'),
+ user: 'Admin'
+ }
+];
+
+/**
+ * Servicio para gestionar operaciones del almacén
+ */
+export const warehouseService = {
+ /**
+ * Obtiene todos los productos del inventario
+ */
+ async getProducts(): Promise {
+ // Simular llamada API
+ return new Promise((resolve) => {
+ setTimeout(() => resolve(mockProducts), 500);
+ });
+ },
+
+ /**
+ * Obtiene un producto por ID
+ */
+ async getProductById(id: string): Promise {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ const product = mockProducts.find(p => p.id === id);
+ resolve(product || null);
+ }, 300);
+ });
+ },
+
+ /**
+ * Obtiene los movimientos del almacén
+ */
+ async getMovements(): Promise {
+ return new Promise((resolve) => {
+ setTimeout(() => resolve(mockMovements), 500);
+ });
+ },
+
+ /**
+ * Obtiene las estadísticas del almacén
+ */
+ async getStats(): Promise {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ const stats: WarehouseStats = {
+ totalProducts: mockProducts.reduce((sum, p) => sum + p.quantity, 0),
+ totalValue: mockProducts.reduce((sum, p) => sum + (p.quantity * p.unitPrice), 0),
+ lowStockItems: mockProducts.filter(p => p.quantity < p.minStock).length,
+ recentMovements: mockMovements.length
+ };
+ resolve(stats);
+ }, 300);
+ });
+ },
+
+ /**
+ * Crea un nuevo movimiento de inventario
+ */
+ async createMovement(movement: Omit): Promise {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ const newMovement: WarehouseMovement = {
+ ...movement,
+ id: String(mockMovements.length + 1),
+ date: new Date()
+ };
+ mockMovements.push(newMovement);
+ resolve(newMovement);
+ }, 500);
+ });
+ },
+
+ /**
+ * Actualiza la cantidad de un producto
+ */
+ async updateProductQuantity(productId: string, quantity: number): Promise {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ const product = mockProducts.find(p => p.id === productId);
+ if (product) {
+ product.quantity = quantity;
+ product.lastUpdated = new Date();
+ resolve(product);
+ } else {
+ resolve(null);
+ }
+ }, 500);
+ });
+ }
+};
diff --git a/src/modules/warehouse/types/warehouse.d.ts b/src/modules/warehouse/types/warehouse.d.ts
new file mode 100644
index 0000000..e6f76e3
--- /dev/null
+++ b/src/modules/warehouse/types/warehouse.d.ts
@@ -0,0 +1,37 @@
+// Types para el módulo Warehouse
+export interface Product {
+ id: string;
+ name: string;
+ sku: string;
+ category: string;
+ quantity: number;
+ minStock: number;
+ maxStock: number;
+ unitPrice: number;
+ location: string;
+ lastUpdated: Date;
+}
+
+export interface WarehouseMovement {
+ id: string;
+ productId: string;
+ productName: string;
+ type: 'in' | 'out' | 'adjustment';
+ quantity: number;
+ reason: string;
+ date: Date;
+ user: string;
+}
+
+export interface WarehouseStats {
+ totalProducts: number;
+ totalValue: number;
+ lowStockItems: number;
+ recentMovements: number;
+}
+
+export interface InventoryFilter {
+ search?: string;
+ category?: string;
+ lowStock?: boolean;
+}
diff --git a/src/services/api.ts b/src/services/api.ts
new file mode 100644
index 0000000..0af79ab
--- /dev/null
+++ b/src/services/api.ts
@@ -0,0 +1,120 @@
+// Servicio API base para todas las peticiones HTTP
+
+const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000/api';
+
+interface RequestOptions extends RequestInit {
+ params?: Record;
+}
+
+class ApiService {
+ private baseUrl: string;
+
+ constructor(baseUrl: string) {
+ this.baseUrl = baseUrl;
+ }
+
+ /**
+ * Construye la URL con query params
+ */
+ private buildUrl(endpoint: string, params?: Record): string {
+ const url = new URL(`${this.baseUrl}${endpoint}`);
+ if (params) {
+ Object.entries(params).forEach(([key, value]) => {
+ url.searchParams.append(key, value);
+ });
+ }
+ return url.toString();
+ }
+
+ /**
+ * Maneja la respuesta de la API
+ */
+ private async handleResponse(response: Response): Promise {
+ if (!response.ok) {
+ const error = await response.json().catch(() => ({ message: 'Error desconocido' }));
+ throw new Error(error.message || `HTTP Error: ${response.status}`);
+ }
+ return response.json();
+ }
+
+ /**
+ * GET request
+ */
+ async get(endpoint: string, options?: RequestOptions): Promise {
+ const url = this.buildUrl(endpoint, options?.params);
+ const response = await fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options?.headers,
+ },
+ });
+ return this.handleResponse(response);
+ }
+
+ /**
+ * POST request
+ */
+ async post(endpoint: string, data?: any, options?: RequestOptions): Promise {
+ const url = this.buildUrl(endpoint, options?.params);
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options?.headers,
+ },
+ body: JSON.stringify(data),
+ });
+ return this.handleResponse(response);
+ }
+
+ /**
+ * PUT request
+ */
+ async put(endpoint: string, data?: any, options?: RequestOptions): Promise {
+ const url = this.buildUrl(endpoint, options?.params);
+ const response = await fetch(url, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options?.headers,
+ },
+ body: JSON.stringify(data),
+ });
+ return this.handleResponse(response);
+ }
+
+ /**
+ * DELETE request
+ */
+ async delete(endpoint: string, options?: RequestOptions): Promise {
+ const url = this.buildUrl(endpoint, options?.params);
+ const response = await fetch(url, {
+ method: 'DELETE',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options?.headers,
+ },
+ });
+ return this.handleResponse(response);
+ }
+
+ /**
+ * PATCH request
+ */
+ async patch(endpoint: string, data?: any, options?: RequestOptions): Promise {
+ const url = this.buildUrl(endpoint, options?.params);
+ const response = await fetch(url, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options?.headers,
+ },
+ body: JSON.stringify(data),
+ });
+ return this.handleResponse(response);
+ }
+}
+
+// Exportar instancia singleton
+export const api = new ApiService(API_BASE_URL);
diff --git a/src/types/global.d.ts b/src/types/global.d.ts
new file mode 100644
index 0000000..972452c
--- /dev/null
+++ b/src/types/global.d.ts
@@ -0,0 +1,39 @@
+// Tipos globales de la aplicación
+
+export interface ApiResponse {
+ data: T;
+ message?: string;
+ success: boolean;
+}
+
+export interface PaginatedResponse {
+ data: T[];
+ total: number;
+ page: number;
+ pageSize: number;
+}
+
+export interface User {
+ id: string;
+ name: string;
+ email: string;
+ role: string;
+}
+
+export interface TableColumn {
+ field: string;
+ header: string;
+ sortable?: boolean;
+ filterable?: boolean;
+}
+
+export type SortOrder = 'asc' | 'desc';
+
+export interface SortOptions {
+ field: string;
+ order: SortOrder;
+}
+
+export interface FilterOptions {
+ [key: string]: any;
+}