-- ============================================================= -- ARCHIVO: 19-product-attributes.sql -- DESCRIPCION: Atributos de productos (color, talla, material) -- VERSION: 1.0.0 -- PROYECTO: ERP-Core V2 -- FECHA: 2026-01-24 -- ============================================================= -- ===================== -- TABLA: product_attributes -- Atributos configurables de productos -- ===================== CREATE TABLE IF NOT EXISTS products.product_attributes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, -- Identificacion code VARCHAR(50) NOT NULL, name VARCHAR(100) NOT NULL, description TEXT, -- Tipo de visualizacion display_type VARCHAR(20) DEFAULT 'radio', -- radio, select, color, pills -- Estado is_active BOOLEAN DEFAULT TRUE, sort_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), UNIQUE(tenant_id, code) ); -- Indices para product_attributes CREATE INDEX IF NOT EXISTS idx_product_attributes_tenant ON products.product_attributes(tenant_id); CREATE INDEX IF NOT EXISTS idx_product_attributes_code ON products.product_attributes(code); CREATE INDEX IF NOT EXISTS idx_product_attributes_active ON products.product_attributes(is_active) WHERE is_active = TRUE; -- ===================== -- TABLA: product_attribute_values -- Valores posibles para cada atributo -- ===================== CREATE TABLE IF NOT EXISTS products.product_attribute_values ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), attribute_id UUID NOT NULL REFERENCES products.product_attributes(id) ON DELETE CASCADE, -- Identificacion code VARCHAR(50), name VARCHAR(100) NOT NULL, -- Visualizacion html_color VARCHAR(20), -- Para atributos de tipo color (#FF0000) image_url VARCHAR(500), -- Imagen del valor -- Estado is_active BOOLEAN DEFAULT TRUE, sort_order INTEGER DEFAULT 0, -- Metadata created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); -- Indices para product_attribute_values CREATE INDEX IF NOT EXISTS idx_product_attribute_values_attribute ON products.product_attribute_values(attribute_id); CREATE INDEX IF NOT EXISTS idx_product_attribute_values_active ON products.product_attribute_values(is_active) WHERE is_active = TRUE; -- ===================== -- TABLA: product_variants -- Variantes de productos (combinaciones de atributos) -- ===================== CREATE TABLE IF NOT EXISTS products.product_variants ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), product_id UUID NOT NULL REFERENCES products.products(id) ON DELETE CASCADE, tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, -- Identificacion sku VARCHAR(50) NOT NULL, barcode VARCHAR(50), name VARCHAR(200) NOT NULL, -- Nombre de la variante (ej: "Camisa Azul - Talla M") -- Precios (override del producto base) price_extra DECIMAL(15, 4) DEFAULT 0, -- Ajuste de precio sobre el producto base cost_extra DECIMAL(15, 4) DEFAULT 0, -- Ajuste de costo sobre el producto base -- Inventario stock_qty DECIMAL(15, 4) DEFAULT 0, -- Imagen image_url VARCHAR(500), -- Estado is_active 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), UNIQUE(tenant_id, sku) ); -- Indices para product_variants CREATE INDEX IF NOT EXISTS idx_product_variants_product ON products.product_variants(product_id); CREATE INDEX IF NOT EXISTS idx_product_variants_tenant ON products.product_variants(tenant_id); CREATE INDEX IF NOT EXISTS idx_product_variants_sku ON products.product_variants(sku); CREATE INDEX IF NOT EXISTS idx_product_variants_barcode ON products.product_variants(barcode); CREATE INDEX IF NOT EXISTS idx_product_variants_active ON products.product_variants(is_active) WHERE is_active = TRUE; -- ===================== -- TABLA: product_variant_attributes -- Relacion M:N entre variantes y valores de atributos -- ===================== CREATE TABLE IF NOT EXISTS products.product_variant_attributes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), variant_id UUID NOT NULL REFERENCES products.product_variants(id) ON DELETE CASCADE, attribute_value_id UUID NOT NULL REFERENCES products.product_attribute_values(id) ON DELETE CASCADE, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, UNIQUE(variant_id, attribute_value_id) ); -- Indices para product_variant_attributes CREATE INDEX IF NOT EXISTS idx_product_variant_attributes_variant ON products.product_variant_attributes(variant_id); CREATE INDEX IF NOT EXISTS idx_product_variant_attributes_value ON products.product_variant_attributes(attribute_value_id); -- ===================== -- COMENTARIOS -- ===================== COMMENT ON TABLE products.product_attributes IS 'Definicion de atributos de productos (color, talla, material, etc.)'; COMMENT ON COLUMN products.product_attributes.display_type IS 'Tipo de visualizacion: radio (botones radio), select (dropdown), color (muestra de colores), pills (etiquetas)'; COMMENT ON TABLE products.product_attribute_values IS 'Valores posibles para cada atributo (ej: Rojo, Verde, Azul para atributo Color)'; COMMENT ON COLUMN products.product_attribute_values.html_color IS 'Color HTML para visualizar en UI (#FF0000)'; COMMENT ON TABLE products.product_variants IS 'Variantes de productos generadas a partir de combinaciones de atributos'; COMMENT ON COLUMN products.product_variants.price_extra IS 'Ajuste de precio sobre el precio base del producto'; COMMENT ON COLUMN products.product_variants.cost_extra IS 'Ajuste de costo sobre el costo base del producto'; COMMENT ON TABLE products.product_variant_attributes IS 'Tabla de union entre variantes y valores de atributos'; -- ===================== -- RLS (Row Level Security) -- ===================== ALTER TABLE products.product_attributes ENABLE ROW LEVEL SECURITY; ALTER TABLE products.product_attribute_values ENABLE ROW LEVEL SECURITY; ALTER TABLE products.product_variants ENABLE ROW LEVEL SECURITY; ALTER TABLE products.product_variant_attributes ENABLE ROW LEVEL SECURITY; -- Politicas RLS para product_attributes DROP POLICY IF EXISTS product_attributes_tenant_isolation ON products.product_attributes; CREATE POLICY product_attributes_tenant_isolation ON products.product_attributes FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); -- Politicas RLS para product_variants DROP POLICY IF EXISTS product_variants_tenant_isolation ON products.product_variants; CREATE POLICY product_variants_tenant_isolation ON products.product_variants FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); -- Politicas RLS para product_attribute_values (via JOIN con attribute) DROP POLICY IF EXISTS product_attribute_values_tenant_isolation ON products.product_attribute_values; CREATE POLICY product_attribute_values_tenant_isolation ON products.product_attribute_values FOR ALL USING ( attribute_id IN ( SELECT id FROM products.product_attributes WHERE tenant_id = current_setting('app.current_tenant_id', true)::uuid ) ); -- Politicas RLS para product_variant_attributes (via JOIN con variant) DROP POLICY IF EXISTS product_variant_attributes_tenant_isolation ON products.product_variant_attributes; CREATE POLICY product_variant_attributes_tenant_isolation ON products.product_variant_attributes FOR ALL USING ( variant_id IN ( SELECT id FROM products.product_variants WHERE tenant_id = current_setting('app.current_tenant_id', true)::uuid ) );