-- ============================================================= -- ARCHIVO: 17-products.sql -- DESCRIPCION: Productos, categorias y precios -- VERSION: 1.0.0 -- PROYECTO: ERP-Core V2 -- FECHA: 2026-01-13 -- ============================================================= -- ===================== -- SCHEMA: products -- ===================== CREATE SCHEMA IF NOT EXISTS products; -- ===================== -- TABLA: product_categories -- Categorias jerarquicas de productos -- ===================== CREATE TABLE IF NOT EXISTS products.product_categories ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, parent_id UUID REFERENCES products.product_categories(id) ON DELETE SET NULL, -- Identificacion code VARCHAR(30) NOT NULL, name VARCHAR(100) NOT NULL, description TEXT, -- Jerarquia hierarchy_path TEXT, -- /root/electronics/phones hierarchy_level INTEGER DEFAULT 0, -- Imagen/icono image_url VARCHAR(500), icon VARCHAR(50), color VARCHAR(20), -- Estado is_active BOOLEAN DEFAULT TRUE, display_order INTEGER DEFAULT 0, -- Metadata created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES auth.users(id), updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, updated_by UUID REFERENCES auth.users(id), deleted_at TIMESTAMPTZ, UNIQUE(tenant_id, code) ); -- Indices para product_categories CREATE INDEX IF NOT EXISTS idx_product_categories_tenant ON products.product_categories(tenant_id); CREATE INDEX IF NOT EXISTS idx_product_categories_parent ON products.product_categories(parent_id); CREATE INDEX IF NOT EXISTS idx_product_categories_code ON products.product_categories(code); CREATE INDEX IF NOT EXISTS idx_product_categories_hierarchy ON products.product_categories(hierarchy_path); CREATE INDEX IF NOT EXISTS idx_product_categories_active ON products.product_categories(is_active) WHERE is_active = TRUE; -- ===================== -- TABLA: products -- Productos y servicios -- ===================== CREATE TABLE IF NOT EXISTS products.products ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, category_id UUID REFERENCES products.product_categories(id) ON DELETE SET NULL, -- Identificacion sku VARCHAR(50) NOT NULL, -- Stock Keeping Unit barcode VARCHAR(50), -- EAN, UPC, etc. name VARCHAR(200) NOT NULL, description TEXT, short_description VARCHAR(500), -- Tipo product_type VARCHAR(20) NOT NULL DEFAULT 'product', -- product, service, consumable, kit -- Precios price DECIMAL(15, 4) NOT NULL DEFAULT 0, cost DECIMAL(15, 4) DEFAULT 0, currency VARCHAR(3) DEFAULT 'MXN', tax_included BOOLEAN DEFAULT TRUE, -- Impuestos tax_rate DECIMAL(5, 2) DEFAULT 16.00, -- IVA en Mexico tax_code VARCHAR(20), -- Codigo SAT para facturacion -- Unidad de medida uom VARCHAR(20) DEFAULT 'PZA', -- Unidad de medida principal uom_purchase VARCHAR(20), -- Unidad de medida para compras uom_conversion DECIMAL(10, 4) DEFAULT 1, -- Factor de conversion -- Inventario track_inventory BOOLEAN DEFAULT TRUE, min_stock DECIMAL(15, 4) DEFAULT 0, max_stock DECIMAL(15, 4), reorder_point DECIMAL(15, 4), lead_time_days INTEGER DEFAULT 0, -- Caracteristicas fisicas weight DECIMAL(10, 4), -- Peso en kg length DECIMAL(10, 4), -- Dimensiones en cm width DECIMAL(10, 4), height DECIMAL(10, 4), volume DECIMAL(10, 4), -- Volumen en m3 -- Imagenes image_url VARCHAR(500), images JSONB DEFAULT '[]', -- Array de URLs de imagenes -- Atributos attributes JSONB DEFAULT '{}', -- Ejemplo: {"color": "red", "size": "XL", "material": "cotton"} -- Estado is_active BOOLEAN DEFAULT TRUE, is_sellable BOOLEAN DEFAULT TRUE, is_purchasable BOOLEAN DEFAULT TRUE, -- Metadata created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES auth.users(id), updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, updated_by UUID REFERENCES auth.users(id), deleted_at TIMESTAMPTZ, UNIQUE(tenant_id, sku) ); -- Indices para products CREATE INDEX IF NOT EXISTS idx_products_tenant ON products.products(tenant_id); CREATE INDEX IF NOT EXISTS idx_products_category ON products.products(category_id); CREATE INDEX IF NOT EXISTS idx_products_sku ON products.products(sku); CREATE INDEX IF NOT EXISTS idx_products_barcode ON products.products(barcode); CREATE INDEX IF NOT EXISTS idx_products_type ON products.products(product_type); CREATE INDEX IF NOT EXISTS idx_products_active ON products.products(is_active) WHERE is_active = TRUE; CREATE INDEX IF NOT EXISTS idx_products_name ON products.products USING gin(to_tsvector('spanish', name)); CREATE INDEX IF NOT EXISTS idx_products_sellable ON products.products(is_sellable) WHERE is_sellable = TRUE; CREATE INDEX IF NOT EXISTS idx_products_purchasable ON products.products(is_purchasable) WHERE is_purchasable = TRUE; -- ===================== -- TABLA: product_prices -- Listas de precios y precios especiales -- ===================== CREATE TABLE IF NOT EXISTS products.product_prices ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), product_id UUID NOT NULL REFERENCES products.products(id) ON DELETE CASCADE, -- Tipo de precio price_type VARCHAR(30) NOT NULL DEFAULT 'standard', -- standard, wholesale, retail, promo price_list_name VARCHAR(100), -- Precio price DECIMAL(15, 4) NOT NULL, currency VARCHAR(3) DEFAULT 'MXN', -- Cantidad minima para este precio min_quantity DECIMAL(15, 4) DEFAULT 1, -- Vigencia valid_from TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, valid_to TIMESTAMPTZ, -- Estado is_active BOOLEAN DEFAULT TRUE, -- Metadata created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); -- Indices para product_prices CREATE INDEX IF NOT EXISTS idx_product_prices_product ON products.product_prices(product_id); CREATE INDEX IF NOT EXISTS idx_product_prices_type ON products.product_prices(price_type); CREATE INDEX IF NOT EXISTS idx_product_prices_active ON products.product_prices(is_active) WHERE is_active = TRUE; CREATE INDEX IF NOT EXISTS idx_product_prices_validity ON products.product_prices(valid_from, valid_to); -- ===================== -- TABLA: product_suppliers -- Proveedores de productos -- ===================== CREATE TABLE IF NOT EXISTS products.product_suppliers ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), product_id UUID NOT NULL REFERENCES products.products(id) ON DELETE CASCADE, supplier_id UUID NOT NULL REFERENCES partners.partners(id) ON DELETE CASCADE, -- Datos del proveedor supplier_sku VARCHAR(50), -- SKU del proveedor supplier_name VARCHAR(200), -- Nombre del producto del proveedor -- Precios de compra purchase_price DECIMAL(15, 4), currency VARCHAR(3) DEFAULT 'MXN', min_order_qty DECIMAL(15, 4) DEFAULT 1, -- Tiempos lead_time_days INTEGER DEFAULT 0, -- Estado is_preferred BOOLEAN DEFAULT FALSE, is_active BOOLEAN DEFAULT TRUE, -- Metadata created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, UNIQUE(product_id, supplier_id) ); -- Indices para product_suppliers CREATE INDEX IF NOT EXISTS idx_product_suppliers_product ON products.product_suppliers(product_id); CREATE INDEX IF NOT EXISTS idx_product_suppliers_supplier ON products.product_suppliers(supplier_id); CREATE INDEX IF NOT EXISTS idx_product_suppliers_preferred ON products.product_suppliers(product_id, is_preferred) WHERE is_preferred = TRUE; -- ===================== -- COMENTARIOS -- ===================== COMMENT ON TABLE products.product_categories IS 'Categorias jerarquicas para organizar productos'; COMMENT ON COLUMN products.product_categories.hierarchy_path IS 'Path materializado para consultas eficientes de jerarquia'; COMMENT ON TABLE products.products IS 'Catalogo de productos y servicios'; COMMENT ON COLUMN products.products.product_type IS 'Tipo: product (fisico), service (servicio), consumable (consumible), kit (combo)'; COMMENT ON COLUMN products.products.sku IS 'Stock Keeping Unit - identificador unico del producto'; COMMENT ON COLUMN products.products.tax_code IS 'Codigo SAT para facturacion electronica en Mexico'; COMMENT ON COLUMN products.products.track_inventory IS 'Si se debe llevar control de inventario'; COMMENT ON TABLE products.product_prices IS 'Listas de precios y precios especiales por cantidad'; COMMENT ON COLUMN products.product_prices.price_type IS 'Tipo: standard, wholesale (mayoreo), retail (menudeo), promo (promocional)'; COMMENT ON TABLE products.product_suppliers IS 'Relacion de productos con sus proveedores'; COMMENT ON COLUMN products.product_suppliers.is_preferred IS 'Proveedor preferido para este producto';