erp-core-database/ddl/17-products.sql
rckrdmrd 5043a640e4 refactor: Restructure DDL with numbered schema files
- 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>
2026-01-16 00:40:32 -06:00

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';