-- =============================================================
-- ARCHIVO: 20-core-catalogs.sql
-- DESCRIPCION: Catalogos maestros - paises, estados, monedas, UoM
-- VERSION: 1.0.0
-- PROYECTO: ERP-Core V2
-- FECHA: 2026-01-18
-- =============================================================

-- =====================
-- SCHEMA: core (si no existe)
-- =====================
CREATE SCHEMA IF NOT EXISTS core;

-- =====================
-- TABLA: countries
-- Paises ISO 3166-1
-- =====================
CREATE TABLE IF NOT EXISTS core.countries (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    code VARCHAR(2) NOT NULL UNIQUE,  -- ISO 3166-1 alpha-2
    code_alpha3 VARCHAR(3),           -- ISO 3166-1 alpha-3
    name VARCHAR(255) NOT NULL,
    phone_code VARCHAR(10),
    currency_code VARCHAR(3),         -- Default currency

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX IF NOT EXISTS idx_countries_code ON core.countries(code);
CREATE INDEX IF NOT EXISTS idx_countries_name ON core.countries(name);

COMMENT ON TABLE core.countries IS 'Catalogo de paises ISO 3166-1';

-- =====================
-- TABLA: states
-- Estados/Provincias/Regiones
-- =====================
CREATE TABLE IF NOT EXISTS core.states (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    country_id UUID NOT NULL REFERENCES core.countries(id) ON DELETE CASCADE,
    code VARCHAR(10) NOT NULL,        -- Codigo del estado (ej: JAL, NL, QRO)
    name VARCHAR(255) NOT NULL,

    -- Datos adicionales
    timezone VARCHAR(50),             -- Zona horaria predominante
    is_active BOOLEAN DEFAULT TRUE,

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,

    UNIQUE(country_id, code)
);

CREATE INDEX IF NOT EXISTS idx_states_country ON core.states(country_id);
CREATE INDEX IF NOT EXISTS idx_states_code ON core.states(code);
CREATE INDEX IF NOT EXISTS idx_states_active ON core.states(is_active) WHERE is_active = TRUE;

COMMENT ON TABLE core.states IS 'Catalogo de estados/provincias/regiones por pais';

-- =====================
-- TABLA: currencies
-- Monedas ISO 4217
-- =====================
CREATE TABLE IF NOT EXISTS core.currencies (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    code VARCHAR(3) NOT NULL UNIQUE,  -- ISO 4217
    name VARCHAR(100) NOT NULL,
    symbol VARCHAR(10) NOT NULL,
    decimals INTEGER NOT NULL DEFAULT 2,
    rounding DECIMAL(12, 6) DEFAULT 0.01,
    active BOOLEAN DEFAULT TRUE,

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX IF NOT EXISTS idx_currencies_code ON core.currencies(code);
CREATE INDEX IF NOT EXISTS idx_currencies_active ON core.currencies(active) WHERE active = TRUE;

COMMENT ON TABLE core.currencies IS 'Catalogo de monedas ISO 4217';

-- =====================
-- TABLA: currency_rates
-- Tipos de cambio historicos
-- =====================
CREATE TABLE IF NOT EXISTS core.currency_rates (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID REFERENCES auth.tenants(id) ON DELETE CASCADE,  -- NULL = global

    from_currency_id UUID NOT NULL REFERENCES core.currencies(id),
    to_currency_id UUID NOT NULL REFERENCES core.currencies(id),

    rate DECIMAL(18, 8) NOT NULL,     -- Tipo de cambio
    rate_date DATE NOT NULL,          -- Fecha del tipo de cambio

    -- Fuente del tipo de cambio
    source VARCHAR(50) DEFAULT 'manual',  -- manual, banxico, xe, openexchange

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    created_by UUID REFERENCES auth.users(id),

    UNIQUE(tenant_id, from_currency_id, to_currency_id, rate_date)
);

CREATE INDEX IF NOT EXISTS idx_currency_rates_tenant ON core.currency_rates(tenant_id);
CREATE INDEX IF NOT EXISTS idx_currency_rates_from ON core.currency_rates(from_currency_id);
CREATE INDEX IF NOT EXISTS idx_currency_rates_to ON core.currency_rates(to_currency_id);
CREATE INDEX IF NOT EXISTS idx_currency_rates_date ON core.currency_rates(rate_date DESC);
CREATE INDEX IF NOT EXISTS idx_currency_rates_lookup ON core.currency_rates(from_currency_id, to_currency_id, rate_date DESC);

COMMENT ON TABLE core.currency_rates IS 'Historico de tipos de cambio entre monedas';

-- =====================
-- TABLA: uom_categories
-- Categorias de unidades de medida
-- =====================
CREATE TABLE IF NOT EXISTS core.uom_categories (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID REFERENCES auth.tenants(id) ON DELETE CASCADE,  -- NULL = global

    name VARCHAR(100) NOT NULL,
    description TEXT,

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,

    UNIQUE(tenant_id, name)
);

CREATE INDEX IF NOT EXISTS idx_uom_categories_tenant ON core.uom_categories(tenant_id);

COMMENT ON TABLE core.uom_categories IS 'Categorias de unidades de medida (peso, volumen, longitud, etc.)';

-- =====================
-- TABLA: uom
-- Unidades de medida
-- =====================
CREATE TABLE IF NOT EXISTS core.uom (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID REFERENCES auth.tenants(id) ON DELETE CASCADE,  -- NULL = global
    category_id UUID NOT NULL REFERENCES core.uom_categories(id) ON DELETE CASCADE,

    name VARCHAR(100) NOT NULL,
    symbol VARCHAR(20) NOT NULL,

    -- Tipo: reference = unidad base de la categoria
    uom_type VARCHAR(20) NOT NULL DEFAULT 'reference',  -- reference, bigger, smaller

    -- Factor de conversion respecto a la unidad de referencia de la categoria
    factor DECIMAL(18, 8) NOT NULL DEFAULT 1,
    rounding DECIMAL(12, 6) DEFAULT 0.01,

    is_active BOOLEAN DEFAULT TRUE,

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,

    UNIQUE(tenant_id, category_id, name)
);

CREATE INDEX IF NOT EXISTS idx_uom_tenant ON core.uom(tenant_id);
CREATE INDEX IF NOT EXISTS idx_uom_category ON core.uom(category_id);
CREATE INDEX IF NOT EXISTS idx_uom_active ON core.uom(is_active) WHERE is_active = TRUE;

COMMENT ON TABLE core.uom IS 'Unidades de medida con factores de conversion';

-- =====================
-- TABLA: product_categories
-- Categorias jerarquicas de productos
-- =====================
CREATE TABLE IF NOT EXISTS core.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 core.product_categories(id) ON DELETE SET NULL,

    code VARCHAR(50),
    name VARCHAR(255) NOT NULL,
    description TEXT,

    -- Jerarquia
    hierarchy_path TEXT,
    hierarchy_level INTEGER DEFAULT 0,

    -- Configuracion
    is_active BOOLEAN DEFAULT TRUE,

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    deleted_at TIMESTAMPTZ,

    UNIQUE(tenant_id, code)
);

CREATE INDEX IF NOT EXISTS idx_product_categories_tenant ON core.product_categories(tenant_id);
CREATE INDEX IF NOT EXISTS idx_product_categories_parent ON core.product_categories(parent_id);
CREATE INDEX IF NOT EXISTS idx_product_categories_path ON core.product_categories(hierarchy_path);
CREATE INDEX IF NOT EXISTS idx_product_categories_active ON core.product_categories(is_active) WHERE is_active = TRUE;

COMMENT ON TABLE core.product_categories IS 'Categorias jerarquicas de productos';

-- =====================
-- TABLA: sequences
-- Secuencias para numeracion automatica
-- =====================
CREATE TABLE IF NOT EXISTS core.sequences (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,

    code VARCHAR(50) NOT NULL,
    name VARCHAR(255) NOT NULL,
    description TEXT,

    -- Formato
    prefix VARCHAR(20),
    suffix VARCHAR(20),
    padding INTEGER DEFAULT 5,

    -- Contador
    next_number BIGINT DEFAULT 1,

    -- Reset
    reset_period VARCHAR(20) DEFAULT 'never',  -- never, daily, monthly, yearly
    last_reset_at TIMESTAMPTZ,

    is_active BOOLEAN DEFAULT TRUE,

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,

    UNIQUE(tenant_id, code)
);

CREATE INDEX IF NOT EXISTS idx_sequences_tenant ON core.sequences(tenant_id);
CREATE INDEX IF NOT EXISTS idx_sequences_code ON core.sequences(code);

COMMENT ON TABLE core.sequences IS 'Secuencias para numeracion automatica de documentos';

-- =====================
-- TABLA: payment_terms
-- Condiciones de pago
-- =====================
CREATE TABLE IF NOT EXISTS core.payment_terms (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,

    code VARCHAR(50) NOT NULL,
    name VARCHAR(255) NOT NULL,
    description TEXT,

    -- Configuracion
    is_immediate BOOLEAN DEFAULT FALSE,  -- Pago inmediato/contado

    is_active BOOLEAN DEFAULT TRUE,

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    deleted_at TIMESTAMPTZ,

    UNIQUE(tenant_id, code)
);

CREATE INDEX IF NOT EXISTS idx_payment_terms_tenant ON core.payment_terms(tenant_id);
CREATE INDEX IF NOT EXISTS idx_payment_terms_code ON core.payment_terms(code);

-- =====================
-- TABLA: payment_term_lines
-- Lineas de condiciones de pago
-- =====================
CREATE TABLE IF NOT EXISTS core.payment_term_lines (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    payment_term_id UUID NOT NULL REFERENCES core.payment_terms(id) ON DELETE CASCADE,

    sequence INTEGER DEFAULT 0,

    line_type VARCHAR(20) NOT NULL DEFAULT 'balance',  -- percent, fixed, balance
    value_percent DECIMAL(5, 2),                        -- Para type=percent
    value_fixed DECIMAL(12, 2),                         -- Para type=fixed

    days INTEGER DEFAULT 0,                             -- Dias para vencimiento
    day_of_month INTEGER,                               -- Dia especifico del mes (1-31)

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX IF NOT EXISTS idx_payment_term_lines_term ON core.payment_term_lines(payment_term_id);

COMMENT ON TABLE core.payment_term_lines IS 'Lineas que componen una condicion de pago';

-- =====================
-- TABLA: discount_rules
-- Reglas de descuento
-- =====================
CREATE TABLE IF NOT EXISTS core.discount_rules (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,

    code VARCHAR(50) NOT NULL,
    name VARCHAR(255) NOT NULL,
    description TEXT,

    -- Tipo de descuento
    discount_type VARCHAR(20) NOT NULL DEFAULT 'percent',  -- percent, fixed
    value DECIMAL(12, 2) NOT NULL,

    -- A que aplica
    applies_to VARCHAR(30) DEFAULT 'all',  -- all, product, category, customer, order

    -- Condiciones (JSONB para flexibilidad)
    conditions JSONB DEFAULT '{}',
    -- Ejemplo: {"min_qty": 10, "min_amount": 1000, "product_ids": [...]}

    -- Vigencia
    valid_from TIMESTAMPTZ,
    valid_until TIMESTAMPTZ,

    -- Prioridad (para resolver conflictos)
    priority INTEGER DEFAULT 0,

    is_active BOOLEAN DEFAULT TRUE,

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    deleted_at TIMESTAMPTZ,

    UNIQUE(tenant_id, code)
);

CREATE INDEX IF NOT EXISTS idx_discount_rules_tenant ON core.discount_rules(tenant_id);
CREATE INDEX IF NOT EXISTS idx_discount_rules_code ON core.discount_rules(code);
CREATE INDEX IF NOT EXISTS idx_discount_rules_active ON core.discount_rules(is_active) WHERE is_active = TRUE;
CREATE INDEX IF NOT EXISTS idx_discount_rules_validity ON core.discount_rules(valid_from, valid_until);

COMMENT ON TABLE core.discount_rules IS 'Reglas de descuento configurables';

-- =====================
-- RLS POLICIES
-- =====================

-- Currency rates: tenant isolation (NULL tenant = global, available to all)
ALTER TABLE core.currency_rates ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_currency_rates ON core.currency_rates
    USING (
        tenant_id IS NULL OR
        tenant_id = current_setting('app.current_tenant_id', true)::uuid
    );

-- UoM Categories: tenant isolation
ALTER TABLE core.uom_categories ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_uom_categories ON core.uom_categories
    USING (
        tenant_id IS NULL OR
        tenant_id = current_setting('app.current_tenant_id', true)::uuid
    );

-- UoM: tenant isolation
ALTER TABLE core.uom ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_uom ON core.uom
    USING (
        tenant_id IS NULL OR
        tenant_id = current_setting('app.current_tenant_id', true)::uuid
    );

-- Product Categories: tenant isolation
ALTER TABLE core.product_categories ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_product_categories ON core.product_categories
    USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);

-- Sequences: tenant isolation
ALTER TABLE core.sequences ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_sequences ON core.sequences
    USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);

-- Payment Terms: tenant isolation
ALTER TABLE core.payment_terms ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_payment_terms ON core.payment_terms
    USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);

-- Discount Rules: tenant isolation
ALTER TABLE core.discount_rules ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_discount_rules ON core.discount_rules
    USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);

-- =====================
-- FUNCIONES DE UTILIDAD
-- =====================

-- Funcion para obtener tipo de cambio mas reciente
CREATE OR REPLACE FUNCTION core.get_currency_rate(
    p_from_currency VARCHAR(3),
    p_to_currency VARCHAR(3),
    p_date DATE DEFAULT CURRENT_DATE,
    p_tenant_id UUID DEFAULT NULL
)
RETURNS DECIMAL(18, 8) AS $$
DECLARE
    v_rate DECIMAL(18, 8);
BEGIN
    -- Si son la misma moneda, retornar 1
    IF p_from_currency = p_to_currency THEN
        RETURN 1.0;
    END IF;

    -- Buscar tipo de cambio directo (mas reciente hasta la fecha dada)
    SELECT cr.rate INTO v_rate
    FROM core.currency_rates cr
    JOIN core.currencies fc ON fc.id = cr.from_currency_id AND fc.code = p_from_currency
    JOIN core.currencies tc ON tc.id = cr.to_currency_id AND tc.code = p_to_currency
    WHERE cr.rate_date <= p_date
      AND (cr.tenant_id IS NULL OR cr.tenant_id = p_tenant_id)
    ORDER BY cr.rate_date DESC, cr.tenant_id DESC NULLS LAST
    LIMIT 1;

    IF v_rate IS NOT NULL THEN
        RETURN v_rate;
    END IF;

    -- Buscar tipo de cambio inverso
    SELECT 1.0 / cr.rate INTO v_rate
    FROM core.currency_rates cr
    JOIN core.currencies fc ON fc.id = cr.from_currency_id AND fc.code = p_to_currency
    JOIN core.currencies tc ON tc.id = cr.to_currency_id AND tc.code = p_from_currency
    WHERE cr.rate_date <= p_date
      AND (cr.tenant_id IS NULL OR cr.tenant_id = p_tenant_id)
    ORDER BY cr.rate_date DESC, cr.tenant_id DESC NULLS LAST
    LIMIT 1;

    RETURN v_rate;  -- NULL si no se encuentra
END;
$$ LANGUAGE plpgsql;

-- Funcion para convertir cantidad entre UoM
CREATE OR REPLACE FUNCTION core.convert_uom(
    p_quantity DECIMAL,
    p_from_uom_id UUID,
    p_to_uom_id UUID
)
RETURNS DECIMAL(18, 8) AS $$
DECLARE
    v_from_factor DECIMAL(18, 8);
    v_to_factor DECIMAL(18, 8);
    v_from_category UUID;
    v_to_category UUID;
BEGIN
    -- Si son la misma UoM, retornar la cantidad
    IF p_from_uom_id = p_to_uom_id THEN
        RETURN p_quantity;
    END IF;

    -- Obtener factores y categorias
    SELECT factor, category_id INTO v_from_factor, v_from_category
    FROM core.uom WHERE id = p_from_uom_id;

    SELECT factor, category_id INTO v_to_factor, v_to_category
    FROM core.uom WHERE id = p_to_uom_id;

    -- Validar que sean de la misma categoria
    IF v_from_category != v_to_category THEN
        RAISE EXCEPTION 'Cannot convert between different UoM categories';
    END IF;

    -- Convertir: primero a unidad de referencia, luego a destino
    RETURN p_quantity * v_from_factor / v_to_factor;
END;
$$ LANGUAGE plpgsql;

-- =====================
-- FIN DEL ARCHIVO
-- =====================
