- Replace old DDL structure with new numbered files (01-24) - Update migrations and seeds for new schema - Clean up deprecated files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
231 lines
8.9 KiB
SQL
231 lines
8.9 KiB
SQL
-- =============================================================
|
|
-- 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';
|