-- ============================================================= -- ARCHIVO: 22-sales.sql -- DESCRIPCION: Cotizaciones y ordenes de venta -- VERSION: 1.0.0 -- PROYECTO: ERP-Core V2 -- FECHA: 2026-01-13 -- DEPENDE DE: 16-partners.sql, 17-products.sql -- ============================================================= -- ===================== -- SCHEMA: sales -- ===================== CREATE SCHEMA IF NOT EXISTS sales; -- ===================== -- TABLA: quotations -- Cotizaciones de venta -- ===================== CREATE TABLE IF NOT EXISTS sales.quotations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, -- Identificacion quotation_number VARCHAR(30) NOT NULL, -- Cliente partner_id UUID NOT NULL REFERENCES partners.partners(id) ON DELETE RESTRICT, partner_name VARCHAR(200), -- Snapshot del nombre partner_email VARCHAR(255), -- Direcciones billing_address JSONB, shipping_address JSONB, -- Fechas quotation_date DATE NOT NULL DEFAULT CURRENT_DATE, valid_until DATE, expected_close_date DATE, -- Vendedor sales_rep_id UUID REFERENCES auth.users(id), -- Totales currency VARCHAR(3) DEFAULT 'MXN', subtotal DECIMAL(15, 2) NOT NULL DEFAULT 0, tax_amount DECIMAL(15, 2) NOT NULL DEFAULT 0, discount_amount DECIMAL(15, 2) NOT NULL DEFAULT 0, total DECIMAL(15, 2) NOT NULL DEFAULT 0, -- Terminos payment_term_days INTEGER DEFAULT 0, payment_method VARCHAR(50), -- Estado status VARCHAR(20) NOT NULL DEFAULT 'draft', -- draft, sent, accepted, rejected, expired, converted -- Conversion converted_to_order BOOLEAN DEFAULT FALSE, order_id UUID, converted_at TIMESTAMPTZ, -- Notas notes TEXT, internal_notes TEXT, terms_and_conditions TEXT, -- 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, quotation_number) ); -- Indices para quotations CREATE INDEX IF NOT EXISTS idx_quotations_tenant ON sales.quotations(tenant_id); CREATE INDEX IF NOT EXISTS idx_quotations_number ON sales.quotations(quotation_number); CREATE INDEX IF NOT EXISTS idx_quotations_partner ON sales.quotations(partner_id); CREATE INDEX IF NOT EXISTS idx_quotations_status ON sales.quotations(status); CREATE INDEX IF NOT EXISTS idx_quotations_date ON sales.quotations(quotation_date); CREATE INDEX IF NOT EXISTS idx_quotations_valid_until ON sales.quotations(valid_until); CREATE INDEX IF NOT EXISTS idx_quotations_sales_rep ON sales.quotations(sales_rep_id); -- ===================== -- TABLA: quotation_items -- Lineas de cotizacion -- ===================== CREATE TABLE IF NOT EXISTS sales.quotation_items ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), quotation_id UUID NOT NULL REFERENCES sales.quotations(id) ON DELETE CASCADE, product_id UUID REFERENCES products.products(id) ON DELETE SET NULL, -- Linea line_number INTEGER NOT NULL DEFAULT 1, -- Producto product_sku VARCHAR(50), product_name VARCHAR(200) NOT NULL, description TEXT, -- Cantidad y precio quantity DECIMAL(15, 4) NOT NULL DEFAULT 1, uom VARCHAR(20) DEFAULT 'PZA', unit_price DECIMAL(15, 4) NOT NULL DEFAULT 0, -- Descuentos discount_percent DECIMAL(5, 2) DEFAULT 0, discount_amount DECIMAL(15, 2) DEFAULT 0, -- Impuestos tax_rate DECIMAL(5, 2) DEFAULT 16.00, tax_amount DECIMAL(15, 2) DEFAULT 0, -- Totales subtotal DECIMAL(15, 2) NOT NULL DEFAULT 0, total DECIMAL(15, 2) NOT NULL DEFAULT 0, -- Metadata created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); -- Indices para quotation_items CREATE INDEX IF NOT EXISTS idx_quotation_items_quotation ON sales.quotation_items(quotation_id); CREATE INDEX IF NOT EXISTS idx_quotation_items_product ON sales.quotation_items(product_id); CREATE INDEX IF NOT EXISTS idx_quotation_items_line ON sales.quotation_items(quotation_id, line_number); -- ===================== -- TABLA: sales_orders -- Ordenes de venta -- ===================== CREATE TABLE IF NOT EXISTS sales.sales_orders ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, -- Identificacion order_number VARCHAR(30) NOT NULL, -- Origen quotation_id UUID REFERENCES sales.quotations(id), -- Cliente partner_id UUID NOT NULL REFERENCES partners.partners(id) ON DELETE RESTRICT, partner_name VARCHAR(200), partner_email VARCHAR(255), -- Direcciones billing_address JSONB, shipping_address JSONB, -- Fechas order_date DATE NOT NULL DEFAULT CURRENT_DATE, requested_date DATE, -- Fecha solicitada por cliente promised_date DATE, -- Fecha prometida shipped_date DATE, delivered_date DATE, -- Vendedor sales_rep_id UUID REFERENCES auth.users(id), -- Almacen warehouse_id UUID REFERENCES inventory.warehouses(id), -- Totales currency VARCHAR(3) DEFAULT 'MXN', subtotal DECIMAL(15, 2) NOT NULL DEFAULT 0, tax_amount DECIMAL(15, 2) NOT NULL DEFAULT 0, discount_amount DECIMAL(15, 2) NOT NULL DEFAULT 0, shipping_amount DECIMAL(15, 2) DEFAULT 0, total DECIMAL(15, 2) NOT NULL DEFAULT 0, -- Terminos payment_term_days INTEGER DEFAULT 0, payment_method VARCHAR(50), -- Estado status VARCHAR(20) NOT NULL DEFAULT 'draft', -- draft, confirmed, processing, shipped, delivered, cancelled -- Envio shipping_method VARCHAR(50), tracking_number VARCHAR(100), carrier VARCHAR(100), -- Notas notes TEXT, internal_notes TEXT, -- 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, order_number) ); -- Indices para sales_orders CREATE INDEX IF NOT EXISTS idx_sales_orders_tenant ON sales.sales_orders(tenant_id); CREATE INDEX IF NOT EXISTS idx_sales_orders_number ON sales.sales_orders(order_number); CREATE INDEX IF NOT EXISTS idx_sales_orders_quotation ON sales.sales_orders(quotation_id); CREATE INDEX IF NOT EXISTS idx_sales_orders_partner ON sales.sales_orders(partner_id); CREATE INDEX IF NOT EXISTS idx_sales_orders_status ON sales.sales_orders(status); CREATE INDEX IF NOT EXISTS idx_sales_orders_date ON sales.sales_orders(order_date); CREATE INDEX IF NOT EXISTS idx_sales_orders_warehouse ON sales.sales_orders(warehouse_id); CREATE INDEX IF NOT EXISTS idx_sales_orders_sales_rep ON sales.sales_orders(sales_rep_id); -- ===================== -- TABLA: sales_order_items -- Lineas de orden de venta -- ===================== CREATE TABLE IF NOT EXISTS sales.sales_order_items ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), order_id UUID NOT NULL REFERENCES sales.sales_orders(id) ON DELETE CASCADE, product_id UUID REFERENCES products.products(id) ON DELETE SET NULL, -- Linea line_number INTEGER NOT NULL DEFAULT 1, -- Producto product_sku VARCHAR(50), product_name VARCHAR(200) NOT NULL, description TEXT, -- Cantidad quantity DECIMAL(15, 4) NOT NULL DEFAULT 1, quantity_reserved DECIMAL(15, 4) DEFAULT 0, quantity_shipped DECIMAL(15, 4) DEFAULT 0, quantity_delivered DECIMAL(15, 4) DEFAULT 0, quantity_returned DECIMAL(15, 4) DEFAULT 0, uom VARCHAR(20) DEFAULT 'PZA', -- Precio unit_price DECIMAL(15, 4) NOT NULL DEFAULT 0, unit_cost DECIMAL(15, 4) DEFAULT 0, -- Descuentos discount_percent DECIMAL(5, 2) DEFAULT 0, discount_amount DECIMAL(15, 2) DEFAULT 0, -- Impuestos tax_rate DECIMAL(5, 2) DEFAULT 16.00, tax_amount DECIMAL(15, 2) DEFAULT 0, -- Totales subtotal DECIMAL(15, 2) NOT NULL DEFAULT 0, total DECIMAL(15, 2) NOT NULL DEFAULT 0, -- Lote/Serie lot_number VARCHAR(50), serial_number VARCHAR(50), -- Estado status VARCHAR(20) DEFAULT 'pending', -- pending, reserved, shipped, delivered, cancelled -- Metadata created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); -- Indices para sales_order_items CREATE INDEX IF NOT EXISTS idx_sales_order_items_order ON sales.sales_order_items(order_id); CREATE INDEX IF NOT EXISTS idx_sales_order_items_product ON sales.sales_order_items(product_id); CREATE INDEX IF NOT EXISTS idx_sales_order_items_line ON sales.sales_order_items(order_id, line_number); CREATE INDEX IF NOT EXISTS idx_sales_order_items_status ON sales.sales_order_items(status); -- ===================== -- COMENTARIOS -- ===================== COMMENT ON TABLE sales.quotations IS 'Cotizaciones de venta a clientes'; COMMENT ON COLUMN sales.quotations.status IS 'Estado: draft, sent, accepted, rejected, expired, converted'; COMMENT ON COLUMN sales.quotations.converted_to_order IS 'Indica si la cotizacion fue convertida a orden de venta'; COMMENT ON TABLE sales.quotation_items IS 'Lineas de detalle de cotizaciones'; COMMENT ON TABLE sales.sales_orders IS 'Ordenes de venta confirmadas'; COMMENT ON COLUMN sales.sales_orders.status IS 'Estado: draft, confirmed, processing, shipped, delivered, cancelled'; COMMENT ON COLUMN sales.sales_orders.quotation_id IS 'Referencia a la cotizacion origen (si aplica)'; COMMENT ON TABLE sales.sales_order_items IS 'Lineas de detalle de ordenes de venta'; COMMENT ON COLUMN sales.sales_order_items.quantity_reserved IS 'Cantidad reservada en inventario'; COMMENT ON COLUMN sales.sales_order_items.quantity_shipped IS 'Cantidad enviada'; COMMENT ON COLUMN sales.sales_order_items.quantity_delivered IS 'Cantidad entregada al cliente';