diff --git a/ddl/19-product-attributes.sql b/ddl/19-product-attributes.sql new file mode 100644 index 0000000..85394b1 --- /dev/null +++ b/ddl/19-product-attributes.sql @@ -0,0 +1,146 @@ +-- ============================================================= +-- 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'; diff --git a/ddl/25-fiscal-catalogs.sql b/ddl/25-fiscal-catalogs.sql new file mode 100644 index 0000000..1b869d7 --- /dev/null +++ b/ddl/25-fiscal-catalogs.sql @@ -0,0 +1,295 @@ +-- ============================================================= +-- ARCHIVO: 21-fiscal-catalogs.sql +-- DESCRIPCION: Catalogos fiscales - SAT Mexico, regimenes, CFDI +-- VERSION: 1.0.0 +-- PROYECTO: ERP-Core V2 +-- FECHA: 2026-01-18 +-- ============================================================= + +-- ===================== +-- SCHEMA: fiscal +-- ===================== +CREATE SCHEMA IF NOT EXISTS fiscal; + +-- ===================== +-- TABLA: tax_categories +-- Categorias de impuestos (IVA, ISR, IEPS, etc.) +-- ===================== +CREATE TABLE IF NOT EXISTS fiscal.tax_categories ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + code VARCHAR(20) NOT NULL UNIQUE, -- IVA, ISR, IEPS, etc. + name VARCHAR(100) NOT NULL, + description TEXT, + + -- Tipo de impuesto + tax_nature VARCHAR(20) NOT NULL DEFAULT 'tax', -- tax, withholding, both + + -- Configuracion SAT Mexico + sat_code VARCHAR(10), -- Codigo SAT (002=IVA, 001=ISR, etc.) + + is_active BOOLEAN DEFAULT TRUE, + + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_tax_categories_code ON fiscal.tax_categories(code); +CREATE INDEX IF NOT EXISTS idx_tax_categories_sat ON fiscal.tax_categories(sat_code); +CREATE INDEX IF NOT EXISTS idx_tax_categories_active ON fiscal.tax_categories(is_active) WHERE is_active = TRUE; + +COMMENT ON TABLE fiscal.tax_categories IS 'Categorias de impuestos (IVA, ISR, IEPS, etc.)'; + +-- ===================== +-- TABLA: fiscal_regimes +-- Regimenes fiscales SAT Mexico (Catalogo c_RegimenFiscal) +-- ===================== +CREATE TABLE IF NOT EXISTS fiscal.fiscal_regimes ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + code VARCHAR(10) NOT NULL UNIQUE, -- Codigo SAT (601, 603, 612, etc.) + name VARCHAR(255) NOT NULL, + description TEXT, + + -- Aplica a persona fisica o moral + applies_to VARCHAR(20) NOT NULL DEFAULT 'both', -- natural (fisica), legal (moral), both + + is_active BOOLEAN DEFAULT TRUE, + + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_fiscal_regimes_code ON fiscal.fiscal_regimes(code); +CREATE INDEX IF NOT EXISTS idx_fiscal_regimes_applies ON fiscal.fiscal_regimes(applies_to); +CREATE INDEX IF NOT EXISTS idx_fiscal_regimes_active ON fiscal.fiscal_regimes(is_active) WHERE is_active = TRUE; + +COMMENT ON TABLE fiscal.fiscal_regimes IS 'Catalogo de regimenes fiscales SAT (c_RegimenFiscal)'; + +-- ===================== +-- TABLA: cfdi_uses +-- Uso del CFDI SAT Mexico (Catalogo c_UsoCFDI) +-- ===================== +CREATE TABLE IF NOT EXISTS fiscal.cfdi_uses ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + code VARCHAR(10) NOT NULL UNIQUE, -- Codigo SAT (G01, G02, G03, etc.) + name VARCHAR(255) NOT NULL, + description TEXT, + + -- Aplica a persona fisica o moral + applies_to VARCHAR(20) NOT NULL DEFAULT 'both', -- natural, legal, both + + -- Regimenes fiscales permitidos (NULL = todos) + allowed_regimes VARCHAR[] DEFAULT NULL, + + is_active BOOLEAN DEFAULT TRUE, + + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_cfdi_uses_code ON fiscal.cfdi_uses(code); +CREATE INDEX IF NOT EXISTS idx_cfdi_uses_applies ON fiscal.cfdi_uses(applies_to); +CREATE INDEX IF NOT EXISTS idx_cfdi_uses_active ON fiscal.cfdi_uses(is_active) WHERE is_active = TRUE; + +COMMENT ON TABLE fiscal.cfdi_uses IS 'Catalogo de uso del CFDI SAT (c_UsoCFDI)'; + +-- ===================== +-- TABLA: payment_methods +-- Formas de pago SAT Mexico (Catalogo c_FormaPago) +-- ===================== +CREATE TABLE IF NOT EXISTS fiscal.payment_methods ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + code VARCHAR(10) NOT NULL UNIQUE, -- Codigo SAT (01, 02, 03, etc.) + name VARCHAR(100) NOT NULL, + description TEXT, + + -- Configuracion + requires_bank_info BOOLEAN DEFAULT FALSE, -- Requiere info bancaria + + is_active BOOLEAN DEFAULT TRUE, + + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_payment_methods_code ON fiscal.payment_methods(code); +CREATE INDEX IF NOT EXISTS idx_payment_methods_active ON fiscal.payment_methods(is_active) WHERE is_active = TRUE; + +COMMENT ON TABLE fiscal.payment_methods IS 'Catalogo de formas de pago SAT (c_FormaPago)'; + +-- ===================== +-- TABLA: payment_types +-- Metodos de pago SAT Mexico (Catalogo c_MetodoPago) +-- ===================== +CREATE TABLE IF NOT EXISTS fiscal.payment_types ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + code VARCHAR(10) NOT NULL UNIQUE, -- PUE, PPD + name VARCHAR(100) NOT NULL, + description TEXT, + + is_active BOOLEAN DEFAULT TRUE, + + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_payment_types_code ON fiscal.payment_types(code); +CREATE INDEX IF NOT EXISTS idx_payment_types_active ON fiscal.payment_types(is_active) WHERE is_active = TRUE; + +COMMENT ON TABLE fiscal.payment_types IS 'Catalogo de metodos de pago SAT (c_MetodoPago)'; + +-- ===================== +-- TABLA: withholding_types +-- Tipos de retencion +-- ===================== +CREATE TABLE IF NOT EXISTS fiscal.withholding_types ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + code VARCHAR(20) NOT NULL UNIQUE, + name VARCHAR(100) NOT NULL, + description TEXT, + + -- Tasa de retencion por defecto + default_rate DECIMAL(5, 2) NOT NULL DEFAULT 0, + + -- Categoria de impuesto asociada + tax_category_id UUID REFERENCES fiscal.tax_categories(id), + + is_active BOOLEAN DEFAULT TRUE, + + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_withholding_types_code ON fiscal.withholding_types(code); +CREATE INDEX IF NOT EXISTS idx_withholding_types_category ON fiscal.withholding_types(tax_category_id); +CREATE INDEX IF NOT EXISTS idx_withholding_types_active ON fiscal.withholding_types(is_active) WHERE is_active = TRUE; + +COMMENT ON TABLE fiscal.withholding_types IS 'Tipos de retencion fiscal'; + +-- ===================== +-- DATOS INICIALES: tax_categories +-- ===================== +INSERT INTO fiscal.tax_categories (code, name, description, tax_nature, sat_code) VALUES + ('IVA', 'Impuesto al Valor Agregado', 'Impuesto al consumo aplicado a bienes y servicios', 'tax', '002'), + ('ISR', 'Impuesto Sobre la Renta', 'Impuesto a los ingresos', 'withholding', '001'), + ('IEPS', 'Impuesto Especial sobre Produccion y Servicios', 'Impuesto a productos especiales', 'tax', '003'), + ('IVA_RET', 'IVA Retenido', 'Retencion de IVA', 'withholding', '002'), + ('ISR_RET', 'ISR Retenido', 'Retencion de ISR', 'withholding', '001'), + ('CEDULAR', 'Impuesto Cedular', 'Impuesto local sobre ingresos', 'tax', NULL), + ('ISH', 'Impuesto Sobre Hospedaje', 'Impuesto estatal al hospedaje', 'tax', NULL) +ON CONFLICT (code) DO NOTHING; + +-- ===================== +-- DATOS INICIALES: fiscal_regimes (SAT Mexico c_RegimenFiscal) +-- ===================== +INSERT INTO fiscal.fiscal_regimes (code, name, applies_to) VALUES + ('601', 'General de Ley Personas Morales', 'legal'), + ('603', 'Personas Morales con Fines no Lucrativos', 'legal'), + ('605', 'Sueldos y Salarios e Ingresos Asimilados a Salarios', 'natural'), + ('606', 'Arrendamiento', 'natural'), + ('607', 'Régimen de Enajenación o Adquisición de Bienes', 'natural'), + ('608', 'Demás ingresos', 'natural'), + ('609', 'Consolidación', 'legal'), + ('610', 'Residentes en el Extranjero sin Establecimiento Permanente en México', 'both'), + ('611', 'Ingresos por Dividendos (socios y accionistas)', 'natural'), + ('612', 'Personas Físicas con Actividades Empresariales y Profesionales', 'natural'), + ('614', 'Ingresos por intereses', 'natural'), + ('615', 'Régimen de los ingresos por obtención de premios', 'natural'), + ('616', 'Sin obligaciones fiscales', 'both'), + ('620', 'Sociedades Cooperativas de Producción que optan por diferir sus ingresos', 'legal'), + ('621', 'Incorporación Fiscal', 'natural'), + ('622', 'Actividades Agrícolas, Ganaderas, Silvícolas y Pesqueras', 'both'), + ('623', 'Opcional para Grupos de Sociedades', 'legal'), + ('624', 'Coordinados', 'legal'), + ('625', 'Régimen de las Actividades Empresariales con ingresos a través de Plataformas Tecnológicas', 'natural'), + ('626', 'Régimen Simplificado de Confianza', 'both') +ON CONFLICT (code) DO NOTHING; + +-- ===================== +-- DATOS INICIALES: cfdi_uses (SAT Mexico c_UsoCFDI) +-- ===================== +INSERT INTO fiscal.cfdi_uses (code, name, applies_to) VALUES + ('G01', 'Adquisición de mercancías', 'both'), + ('G02', 'Devoluciones, descuentos o bonificaciones', 'both'), + ('G03', 'Gastos en general', 'both'), + ('I01', 'Construcciones', 'both'), + ('I02', 'Mobiliario y equipo de oficina por inversiones', 'both'), + ('I03', 'Equipo de transporte', 'both'), + ('I04', 'Equipo de cómputo y accesorios', 'both'), + ('I05', 'Dados, troqueles, moldes, matrices y herramental', 'both'), + ('I06', 'Comunicaciones telefónicas', 'both'), + ('I07', 'Comunicaciones satelitales', 'both'), + ('I08', 'Otra maquinaria y equipo', 'both'), + ('D01', 'Honorarios médicos, dentales y gastos hospitalarios', 'natural'), + ('D02', 'Gastos médicos por incapacidad o discapacidad', 'natural'), + ('D03', 'Gastos funerales', 'natural'), + ('D04', 'Donativos', 'natural'), + ('D05', 'Intereses reales efectivamente pagados por créditos hipotecarios (casa habitación)', 'natural'), + ('D06', 'Aportaciones voluntarias al SAR', 'natural'), + ('D07', 'Primas por seguros de gastos médicos', 'natural'), + ('D08', 'Gastos de transportación escolar obligatoria', 'natural'), + ('D09', 'Depósitos en cuentas para el ahorro, primas que tengan como base planes de pensiones', 'natural'), + ('D10', 'Pagos por servicios educativos (colegiaturas)', 'natural'), + ('S01', 'Sin efectos fiscales', 'both'), + ('CP01', 'Pagos', 'both'), + ('CN01', 'Nómina', 'natural') +ON CONFLICT (code) DO NOTHING; + +-- ===================== +-- DATOS INICIALES: payment_methods (SAT Mexico c_FormaPago) +-- ===================== +INSERT INTO fiscal.payment_methods (code, name, requires_bank_info) VALUES + ('01', 'Efectivo', false), + ('02', 'Cheque nominativo', true), + ('03', 'Transferencia electrónica de fondos', true), + ('04', 'Tarjeta de crédito', true), + ('05', 'Monedero electrónico', false), + ('06', 'Dinero electrónico', false), + ('08', 'Vales de despensa', false), + ('12', 'Dación en pago', false), + ('13', 'Pago por subrogación', false), + ('14', 'Pago por consignación', false), + ('15', 'Condonación', false), + ('17', 'Compensación', false), + ('23', 'Novación', false), + ('24', 'Confusión', false), + ('25', 'Remisión de deuda', false), + ('26', 'Prescripción o caducidad', false), + ('27', 'A satisfacción del acreedor', false), + ('28', 'Tarjeta de débito', true), + ('29', 'Tarjeta de servicios', true), + ('30', 'Aplicación de anticipos', false), + ('31', 'Intermediario pagos', false), + ('99', 'Por definir', false) +ON CONFLICT (code) DO NOTHING; + +-- ===================== +-- DATOS INICIALES: payment_types (SAT Mexico c_MetodoPago) +-- ===================== +INSERT INTO fiscal.payment_types (code, name, description) VALUES + ('PUE', 'Pago en una sola exhibición', 'El pago se realiza en una sola exhibición al momento de emitir el CFDI'), + ('PPD', 'Pago en parcialidades o diferido', 'El pago se realiza en parcialidades o de forma diferida') +ON CONFLICT (code) DO NOTHING; + +-- ===================== +-- DATOS INICIALES: withholding_types +-- ===================== +INSERT INTO fiscal.withholding_types (code, name, description, default_rate, tax_category_id) VALUES + ('ISR_10', 'Retención ISR 10%', 'Retención de ISR al 10% para servicios profesionales', 10.00, + (SELECT id FROM fiscal.tax_categories WHERE code = 'ISR')), + ('ISR_10.67', 'Retención ISR 10.67%', 'Retención de ISR al 10.67% para arrendamiento', 10.67, + (SELECT id FROM fiscal.tax_categories WHERE code = 'ISR')), + ('IVA_RET_10.67', 'Retención IVA 10.67%', 'Retención de IVA para servicios profesionales persona moral', 10.67, + (SELECT id FROM fiscal.tax_categories WHERE code = 'IVA_RET')), + ('IVA_RET_4', 'Retención IVA 4%', 'Retención de IVA al 4% para autotransporte', 4.00, + (SELECT id FROM fiscal.tax_categories WHERE code = 'IVA_RET')) +ON CONFLICT (code) DO NOTHING; + +-- ===================== +-- FIN DEL ARCHIVO +-- =====================