feat(catalogs): Add core catalogs DDL and seed data (MGN-005)

DDL (20-core-catalogs.sql):
- Countries, States tables with ISO codes
- Currencies with ISO 4217
- Currency rates with historical tracking
- UoM categories and units with conversion factors
- Product categories hierarchy
- Sequences, Payment terms, Discount rules

Seed data (04-seed-catalogs.sql):
- 33 countries (Americas, Europe, Asia)
- 32 Mexican states with timezones
- 16 currencies (MXN, USD, EUR, etc.)
- Initial exchange rates
- 6 UoM categories with 30+ units

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rckrdmrd 2026-01-18 08:57:41 -06:00
parent 5043a640e4
commit 49c64e74a8
2 changed files with 726 additions and 0 deletions

467
ddl/20-core-catalogs.sql Normal file
View File

@ -0,0 +1,467 @@
-- =============================================================
-- 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
-- =====================

View File

@ -0,0 +1,259 @@
-- =============================================================
-- ARCHIVO: 04-seed-catalogs.sql
-- DESCRIPCION: Datos semilla para catalogos maestros
-- VERSION: 1.0.0
-- PROYECTO: ERP-Core V2
-- FECHA: 2026-01-18
-- =============================================================
-- =====================
-- PAISES (ISO 3166-1)
-- =====================
INSERT INTO core.countries (code, code_alpha3, name, phone_code, currency_code) VALUES
-- America del Norte
('MX', 'MEX', 'México', '+52', 'MXN'),
('US', 'USA', 'Estados Unidos', '+1', 'USD'),
('CA', 'CAN', 'Canadá', '+1', 'CAD'),
-- America Central
('GT', 'GTM', 'Guatemala', '+502', 'GTQ'),
('BZ', 'BLZ', 'Belice', '+501', 'BZD'),
('SV', 'SLV', 'El Salvador', '+503', 'USD'),
('HN', 'HND', 'Honduras', '+504', 'HNL'),
('NI', 'NIC', 'Nicaragua', '+505', 'NIO'),
('CR', 'CRI', 'Costa Rica', '+506', 'CRC'),
('PA', 'PAN', 'Panamá', '+507', 'PAB'),
-- America del Sur
('CO', 'COL', 'Colombia', '+57', 'COP'),
('VE', 'VEN', 'Venezuela', '+58', 'VES'),
('EC', 'ECU', 'Ecuador', '+593', 'USD'),
('PE', 'PER', 'Perú', '+51', 'PEN'),
('BO', 'BOL', 'Bolivia', '+591', 'BOB'),
('CL', 'CHL', 'Chile', '+56', 'CLP'),
('AR', 'ARG', 'Argentina', '+54', 'ARS'),
('UY', 'URY', 'Uruguay', '+598', 'UYU'),
('PY', 'PRY', 'Paraguay', '+595', 'PYG'),
('BR', 'BRA', 'Brasil', '+55', 'BRL'),
-- Europa
('ES', 'ESP', 'España', '+34', 'EUR'),
('DE', 'DEU', 'Alemania', '+49', 'EUR'),
('FR', 'FRA', 'Francia', '+33', 'EUR'),
('IT', 'ITA', 'Italia', '+39', 'EUR'),
('GB', 'GBR', 'Reino Unido', '+44', 'GBP'),
('PT', 'PRT', 'Portugal', '+351', 'EUR'),
('NL', 'NLD', 'Países Bajos', '+31', 'EUR'),
('BE', 'BEL', 'Bélgica', '+32', 'EUR'),
('CH', 'CHE', 'Suiza', '+41', 'CHF'),
-- Asia
('CN', 'CHN', 'China', '+86', 'CNY'),
('JP', 'JPN', 'Japón', '+81', 'JPY'),
('KR', 'KOR', 'Corea del Sur', '+82', 'KRW'),
('IN', 'IND', 'India', '+91', 'INR')
ON CONFLICT (code) DO NOTHING;
-- =====================
-- ESTADOS DE MEXICO
-- =====================
-- Obtener ID de México
DO $$
DECLARE
mexico_id UUID;
BEGIN
SELECT id INTO mexico_id FROM core.countries WHERE code = 'MX';
IF mexico_id IS NOT NULL THEN
INSERT INTO core.states (country_id, code, name, timezone, is_active) VALUES
(mexico_id, 'AGU', 'Aguascalientes', 'America/Mexico_City', true),
(mexico_id, 'BCN', 'Baja California', 'America/Tijuana', true),
(mexico_id, 'BCS', 'Baja California Sur', 'America/Mazatlan', true),
(mexico_id, 'CAM', 'Campeche', 'America/Merida', true),
(mexico_id, 'CHP', 'Chiapas', 'America/Mexico_City', true),
(mexico_id, 'CHH', 'Chihuahua', 'America/Chihuahua', true),
(mexico_id, 'CMX', 'Ciudad de México', 'America/Mexico_City', true),
(mexico_id, 'COA', 'Coahuila', 'America/Monterrey', true),
(mexico_id, 'COL', 'Colima', 'America/Mexico_City', true),
(mexico_id, 'DUR', 'Durango', 'America/Monterrey', true),
(mexico_id, 'GUA', 'Guanajuato', 'America/Mexico_City', true),
(mexico_id, 'GRO', 'Guerrero', 'America/Mexico_City', true),
(mexico_id, 'HID', 'Hidalgo', 'America/Mexico_City', true),
(mexico_id, 'JAL', 'Jalisco', 'America/Mexico_City', true),
(mexico_id, 'MEX', 'Estado de México', 'America/Mexico_City', true),
(mexico_id, 'MIC', 'Michoacán', 'America/Mexico_City', true),
(mexico_id, 'MOR', 'Morelos', 'America/Mexico_City', true),
(mexico_id, 'NAY', 'Nayarit', 'America/Mazatlan', true),
(mexico_id, 'NLE', 'Nuevo León', 'America/Monterrey', true),
(mexico_id, 'OAX', 'Oaxaca', 'America/Mexico_City', true),
(mexico_id, 'PUE', 'Puebla', 'America/Mexico_City', true),
(mexico_id, 'QUE', 'Querétaro', 'America/Mexico_City', true),
(mexico_id, 'ROO', 'Quintana Roo', 'America/Cancun', true),
(mexico_id, 'SLP', 'San Luis Potosí', 'America/Mexico_City', true),
(mexico_id, 'SIN', 'Sinaloa', 'America/Mazatlan', true),
(mexico_id, 'SON', 'Sonora', 'America/Hermosillo', true),
(mexico_id, 'TAB', 'Tabasco', 'America/Mexico_City', true),
(mexico_id, 'TAM', 'Tamaulipas', 'America/Monterrey', true),
(mexico_id, 'TLA', 'Tlaxcala', 'America/Mexico_City', true),
(mexico_id, 'VER', 'Veracruz', 'America/Mexico_City', true),
(mexico_id, 'YUC', 'Yucatán', 'America/Merida', true),
(mexico_id, 'ZAC', 'Zacatecas', 'America/Mexico_City', true)
ON CONFLICT (country_id, code) DO NOTHING;
END IF;
END $$;
-- =====================
-- MONEDAS (ISO 4217)
-- =====================
INSERT INTO core.currencies (code, name, symbol, decimals, rounding, active) VALUES
-- Americas
('MXN', 'Peso Mexicano', '$', 2, 0.01, true),
('USD', 'Dólar Estadounidense', '$', 2, 0.01, true),
('CAD', 'Dólar Canadiense', '$', 2, 0.01, true),
('BRL', 'Real Brasileño', 'R$', 2, 0.01, true),
('ARS', 'Peso Argentino', '$', 2, 0.01, true),
('CLP', 'Peso Chileno', '$', 0, 1, true),
('COP', 'Peso Colombiano', '$', 0, 1, true),
('PEN', 'Sol Peruano', 'S/', 2, 0.01, true),
('GTQ', 'Quetzal', 'Q', 2, 0.01, true),
-- Europa
('EUR', 'Euro', '', 2, 0.01, true),
('GBP', 'Libra Esterlina', '£', 2, 0.01, true),
('CHF', 'Franco Suizo', 'Fr', 2, 0.01, true),
-- Asia
('JPY', 'Yen Japonés', '¥', 0, 1, true),
('CNY', 'Yuan Chino', '¥', 2, 0.01, true),
('KRW', 'Won Surcoreano', '', 0, 1, true),
('INR', 'Rupia India', '', 2, 0.01, true)
ON CONFLICT (code) DO NOTHING;
-- =====================
-- TIPOS DE CAMBIO INICIALES (MXN base)
-- =====================
DO $$
DECLARE
mxn_id UUID;
usd_id UUID;
eur_id UUID;
cad_id UUID;
BEGIN
SELECT id INTO mxn_id FROM core.currencies WHERE code = 'MXN';
SELECT id INTO usd_id FROM core.currencies WHERE code = 'USD';
SELECT id INTO eur_id FROM core.currencies WHERE code = 'EUR';
SELECT id INTO cad_id FROM core.currencies WHERE code = 'CAD';
IF mxn_id IS NOT NULL AND usd_id IS NOT NULL THEN
INSERT INTO core.currency_rates (tenant_id, from_currency_id, to_currency_id, rate, rate_date, source)
VALUES
(NULL, usd_id, mxn_id, 17.50, CURRENT_DATE, 'manual'),
(NULL, eur_id, mxn_id, 19.20, CURRENT_DATE, 'manual'),
(NULL, cad_id, mxn_id, 13.10, CURRENT_DATE, 'manual')
ON CONFLICT DO NOTHING;
END IF;
END $$;
-- =====================
-- CATEGORIAS DE UOM (Globales)
-- =====================
INSERT INTO core.uom_categories (tenant_id, name, description) VALUES
(NULL, 'Unidad', 'Unidades contables'),
(NULL, 'Peso', 'Unidades de peso/masa'),
(NULL, 'Longitud', 'Unidades de longitud'),
(NULL, 'Volumen', 'Unidades de volumen'),
(NULL, 'Área', 'Unidades de área'),
(NULL, 'Tiempo', 'Unidades de tiempo')
ON CONFLICT (tenant_id, name) DO NOTHING;
-- =====================
-- UOM (Globales)
-- =====================
DO $$
DECLARE
cat_unit UUID;
cat_peso UUID;
cat_longitud UUID;
cat_volumen UUID;
cat_area UUID;
cat_tiempo UUID;
BEGIN
SELECT id INTO cat_unit FROM core.uom_categories WHERE name = 'Unidad' AND tenant_id IS NULL;
SELECT id INTO cat_peso FROM core.uom_categories WHERE name = 'Peso' AND tenant_id IS NULL;
SELECT id INTO cat_longitud FROM core.uom_categories WHERE name = 'Longitud' AND tenant_id IS NULL;
SELECT id INTO cat_volumen FROM core.uom_categories WHERE name = 'Volumen' AND tenant_id IS NULL;
SELECT id INTO cat_area FROM core.uom_categories WHERE name = 'Área' AND tenant_id IS NULL;
SELECT id INTO cat_tiempo FROM core.uom_categories WHERE name = 'Tiempo' AND tenant_id IS NULL;
-- Unidad
IF cat_unit IS NOT NULL THEN
INSERT INTO core.uom (tenant_id, category_id, name, symbol, uom_type, factor, is_active) VALUES
(NULL, cat_unit, 'Pieza', 'pz', 'reference', 1, true),
(NULL, cat_unit, 'Docena', 'doc', 'bigger', 12, true),
(NULL, cat_unit, 'Ciento', 'cto', 'bigger', 100, true),
(NULL, cat_unit, 'Millar', 'mil', 'bigger', 1000, true),
(NULL, cat_unit, 'Par', 'par', 'bigger', 2, true)
ON CONFLICT (tenant_id, category_id, name) DO NOTHING;
END IF;
-- Peso
IF cat_peso IS NOT NULL THEN
INSERT INTO core.uom (tenant_id, category_id, name, symbol, uom_type, factor, is_active) VALUES
(NULL, cat_peso, 'Kilogramo', 'kg', 'reference', 1, true),
(NULL, cat_peso, 'Gramo', 'g', 'smaller', 0.001, true),
(NULL, cat_peso, 'Miligramo', 'mg', 'smaller', 0.000001, true),
(NULL, cat_peso, 'Tonelada', 't', 'bigger', 1000, true),
(NULL, cat_peso, 'Libra', 'lb', 'smaller', 0.453592, true),
(NULL, cat_peso, 'Onza', 'oz', 'smaller', 0.0283495, true)
ON CONFLICT (tenant_id, category_id, name) DO NOTHING;
END IF;
-- Longitud
IF cat_longitud IS NOT NULL THEN
INSERT INTO core.uom (tenant_id, category_id, name, symbol, uom_type, factor, is_active) VALUES
(NULL, cat_longitud, 'Metro', 'm', 'reference', 1, true),
(NULL, cat_longitud, 'Centímetro', 'cm', 'smaller', 0.01, true),
(NULL, cat_longitud, 'Milímetro', 'mm', 'smaller', 0.001, true),
(NULL, cat_longitud, 'Kilómetro', 'km', 'bigger', 1000, true),
(NULL, cat_longitud, 'Pulgada', 'in', 'smaller', 0.0254, true),
(NULL, cat_longitud, 'Pie', 'ft', 'smaller', 0.3048, true),
(NULL, cat_longitud, 'Yarda', 'yd', 'smaller', 0.9144, true)
ON CONFLICT (tenant_id, category_id, name) DO NOTHING;
END IF;
-- Volumen
IF cat_volumen IS NOT NULL THEN
INSERT INTO core.uom (tenant_id, category_id, name, symbol, uom_type, factor, is_active) VALUES
(NULL, cat_volumen, 'Litro', 'L', 'reference', 1, true),
(NULL, cat_volumen, 'Mililitro', 'mL', 'smaller', 0.001, true),
(NULL, cat_volumen, 'Metro cúbico', '', 'bigger', 1000, true),
(NULL, cat_volumen, 'Galón US', 'gal', 'bigger', 3.78541, true),
(NULL, cat_volumen, 'Onza líquida', 'fl oz', 'smaller', 0.0295735, true)
ON CONFLICT (tenant_id, category_id, name) DO NOTHING;
END IF;
-- Área
IF cat_area IS NOT NULL THEN
INSERT INTO core.uom (tenant_id, category_id, name, symbol, uom_type, factor, is_active) VALUES
(NULL, cat_area, 'Metro cuadrado', '', 'reference', 1, true),
(NULL, cat_area, 'Centímetro cuadrado', 'cm²', 'smaller', 0.0001, true),
(NULL, cat_area, 'Kilómetro cuadrado', 'km²', 'bigger', 1000000, true),
(NULL, cat_area, 'Hectárea', 'ha', 'bigger', 10000, true),
(NULL, cat_area, 'Pie cuadrado', 'ft²', 'smaller', 0.092903, true)
ON CONFLICT (tenant_id, category_id, name) DO NOTHING;
END IF;
-- Tiempo
IF cat_tiempo IS NOT NULL THEN
INSERT INTO core.uom (tenant_id, category_id, name, symbol, uom_type, factor, is_active) VALUES
(NULL, cat_tiempo, 'Hora', 'h', 'reference', 1, true),
(NULL, cat_tiempo, 'Minuto', 'min', 'smaller', 0.0166667, true),
(NULL, cat_tiempo, 'Día', 'd', 'bigger', 24, true),
(NULL, cat_tiempo, 'Semana', 'sem', 'bigger', 168, true)
ON CONFLICT (tenant_id, category_id, name) DO NOTHING;
END IF;
END $$;
-- =====================
-- FIN DEL ARCHIVO
-- =====================