-- ============================================================================ -- Archivo: 01-init-database.sql -- Descripcion: Inicializacion completa de base de datos - Carga Limpia -- Proyecto: ERP Suite - Vertical Construccion -- Version: 2.0.0 -- Fecha: 2025-12-06 -- ============================================================================ -- POLITICA: CARGA LIMPIA (ver DIRECTIVA-POLITICA-CARGA-LIMPIA.md) -- Este archivo es parte de la fuente de verdad DDL. -- NO usar migrations, fixes o patches incrementales. -- ============================================================================ -- ============================================================================ -- EXTENSIONES -- ============================================================================ CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- Generacion de UUIDs CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- Funciones criptograficas CREATE EXTENSION IF NOT EXISTS "postgis"; -- Geolocalizacion CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- Busqueda fuzzy CREATE EXTENSION IF NOT EXISTS "btree_gist"; -- Indices GiST avanzados -- ============================================================================ -- SCHEMA: core_shared (funciones compartidas) -- ============================================================================ CREATE SCHEMA IF NOT EXISTS core_shared; COMMENT ON SCHEMA core_shared IS 'Funciones, tipos y utilidades compartidas entre modulos'; -- ============================================================================ -- FUNCIONES DE AUDITORIA -- ============================================================================ -- Funcion para actualizar updated_at automaticamente CREATE OR REPLACE FUNCTION core_shared.set_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; COMMENT ON FUNCTION core_shared.set_updated_at() IS 'Trigger function para actualizar automaticamente el campo updated_at en cada UPDATE'; -- Funcion para establecer tenant_id desde contexto CREATE OR REPLACE FUNCTION core_shared.set_tenant_id() RETURNS TRIGGER AS $$ BEGIN IF NEW.tenant_id IS NULL THEN NEW.tenant_id = current_setting('app.current_tenant_id', true)::uuid; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; COMMENT ON FUNCTION core_shared.set_tenant_id() IS 'Trigger function para establecer tenant_id automaticamente desde el contexto de sesion'; -- Funcion para establecer created_by desde contexto CREATE OR REPLACE FUNCTION core_shared.set_created_by() RETURNS TRIGGER AS $$ BEGIN IF NEW.created_by IS NULL THEN NEW.created_by = current_setting('app.current_user_id', true)::uuid; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; COMMENT ON FUNCTION core_shared.set_created_by() IS 'Trigger function para establecer created_by automaticamente desde el contexto de sesion'; -- ============================================================================ -- FUNCIONES DE CONTEXTO -- ============================================================================ CREATE OR REPLACE FUNCTION core_shared.get_current_tenant_id() RETURNS UUID AS $$ BEGIN RETURN NULLIF(current_setting('app.current_tenant_id', true), '')::UUID; EXCEPTION WHEN OTHERS THEN RETURN NULL; END; $$ LANGUAGE plpgsql STABLE; COMMENT ON FUNCTION core_shared.get_current_tenant_id() IS 'Obtiene el ID del tenant actual desde el contexto de sesion'; CREATE OR REPLACE FUNCTION core_shared.get_current_user_id() RETURNS UUID AS $$ BEGIN RETURN NULLIF(current_setting('app.current_user_id', true), '')::UUID; EXCEPTION WHEN OTHERS THEN RETURN NULL; END; $$ LANGUAGE plpgsql STABLE; COMMENT ON FUNCTION core_shared.get_current_user_id() IS 'Obtiene el ID del usuario actual desde el contexto de sesion'; -- ============================================================================ -- FUNCIONES DE UTILIDAD -- ============================================================================ -- Generar slug desde texto CREATE OR REPLACE FUNCTION core_shared.generate_slug(input_text TEXT) RETURNS TEXT AS $$ BEGIN RETURN LOWER( REGEXP_REPLACE( REGEXP_REPLACE( TRIM(input_text), '[^a-zA-Z0-9\s-]', '', 'g' ), '\s+', '-', 'g' ) ); END; $$ LANGUAGE plpgsql IMMUTABLE; -- Validar formato de email CREATE OR REPLACE FUNCTION core_shared.is_valid_email(email TEXT) RETURNS BOOLEAN AS $$ BEGIN RETURN email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'; END; $$ LANGUAGE plpgsql IMMUTABLE; -- Validar formato de RFC mexicano CREATE OR REPLACE FUNCTION core_shared.is_valid_rfc(rfc TEXT) RETURNS BOOLEAN AS $$ BEGIN RETURN rfc ~* '^[A-Z&Ñ]{3,4}[0-9]{6}[A-Z0-9]{3}$'; END; $$ LANGUAGE plpgsql IMMUTABLE; -- ============================================================================ -- PERMISOS -- ============================================================================ GRANT USAGE ON SCHEMA core_shared TO PUBLIC; GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA core_shared TO PUBLIC; -- ============================================================================ -- SCHEMAS DE NEGOCIO - NOMENCLATURA UNIFICADA -- ============================================================================ -- Segun NAMING-CONVENTIONS.md, usamos nombres cortos y descriptivos: -- - construction (antes project_management, construction_management) -- - estimates (antes financial_management) -- - infonavit (antes infonavit_management) -- - hr (extension de erp-core) -- - inventory (extension de erp-core) -- - purchase (extension de erp-core) -- - hse (nuevo: seguridad, salud, medio ambiente) -- ============================================================================ -- Schemas propios de construccion CREATE SCHEMA IF NOT EXISTS construction; CREATE SCHEMA IF NOT EXISTS estimates; CREATE SCHEMA IF NOT EXISTS infonavit; CREATE SCHEMA IF NOT EXISTS hse; -- Schemas de extension (extendemos modulos de erp-core) -- Estos pueden ya existir si erp-core esta instalado CREATE SCHEMA IF NOT EXISTS hr; CREATE SCHEMA IF NOT EXISTS inventory; CREATE SCHEMA IF NOT EXISTS purchase; -- Schemas legacy (compatibilidad temporal, marcar para deprecacion) -- NOTA: Estos seran eliminados en version futura CREATE SCHEMA IF NOT EXISTS auth_management; CREATE SCHEMA IF NOT EXISTS project_management; CREATE SCHEMA IF NOT EXISTS financial_management; CREATE SCHEMA IF NOT EXISTS purchasing_management; CREATE SCHEMA IF NOT EXISTS inventory_management; CREATE SCHEMA IF NOT EXISTS construction_management; CREATE SCHEMA IF NOT EXISTS quality_management; CREATE SCHEMA IF NOT EXISTS safety_management; CREATE SCHEMA IF NOT EXISTS infonavit_management; -- ============================================================================ -- COMENTARIOS EN SCHEMAS -- ============================================================================ -- Schemas principales (nuevos) COMMENT ON SCHEMA construction IS 'Gestion de obras: proyectos, fraccionamientos, fases, viviendas, avances'; COMMENT ON SCHEMA estimates IS 'Presupuestos, partidas, estimaciones, control de costos'; COMMENT ON SCHEMA infonavit IS 'Integracion INFONAVIT: tramites, ECUVE, subsidios, normatividad'; COMMENT ON SCHEMA hse IS 'Seguridad, Salud Ocupacional y Medio Ambiente (HSE/EHS)'; COMMENT ON SCHEMA hr IS 'Extension de RRHH para construccion: cuadrillas, destajo, asistencia obra'; COMMENT ON SCHEMA inventory IS 'Extension de inventarios: almacenes de obra, control de materiales'; COMMENT ON SCHEMA purchase IS 'Extension de compras: proveedores de construccion, requisiciones de obra'; -- Schemas legacy (deprecated) COMMENT ON SCHEMA auth_management IS 'DEPRECATED: Usar core.auth. Schema mantenido para compatibilidad'; COMMENT ON SCHEMA project_management IS 'DEPRECATED: Usar construction. Schema mantenido para compatibilidad'; COMMENT ON SCHEMA financial_management IS 'DEPRECATED: Usar estimates. Schema mantenido para compatibilidad'; COMMENT ON SCHEMA purchasing_management IS 'DEPRECATED: Usar purchase. Schema mantenido para compatibilidad'; COMMENT ON SCHEMA inventory_management IS 'DEPRECATED: Usar inventory. Schema mantenido para compatibilidad'; COMMENT ON SCHEMA construction_management IS 'DEPRECATED: Usar construction. Schema mantenido para compatibilidad'; COMMENT ON SCHEMA quality_management IS 'DEPRECATED: Funcionalidad movida a hse (inspecciones) y construction (calidad)'; COMMENT ON SCHEMA safety_management IS 'DEPRECATED: Usar hse. Schema mantenido para compatibilidad'; COMMENT ON SCHEMA infonavit_management IS 'DEPRECATED: Usar infonavit. Schema mantenido para compatibilidad'; -- ============================================================================ -- FUNCION LEGACY (compatibilidad) -- ============================================================================ CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = now(); RETURN NEW; END; $$ LANGUAGE plpgsql; COMMENT ON FUNCTION update_updated_at_column() IS 'LEGACY: Usar core_shared.set_updated_at() en nuevas tablas'; -- ============================================================================ -- TABLAS CORE MINIMAS (si erp-core no esta instalado) -- ============================================================================ -- Estas tablas son requeridas como FK por los modulos de construccion -- Si erp-core esta instalado, estas ya existiran y el IF NOT EXISTS las omitira -- Schema core para tablas compartidas CREATE SCHEMA IF NOT EXISTS core; -- Tabla de tenants (multi-tenancy) CREATE TABLE IF NOT EXISTS core.tenants ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), code VARCHAR(50) NOT NULL UNIQUE, name VARCHAR(200) NOT NULL, is_active BOOLEAN NOT NULL DEFAULT true, settings JSONB DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Tabla de usuarios CREATE TABLE IF NOT EXISTS core.users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID REFERENCES core.tenants(id), email VARCHAR(255) NOT NULL, username VARCHAR(100), is_active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE(tenant_id, email) ); -- ============================================================================ -- VERIFICACION -- ============================================================================ DO $$ DECLARE schema_count INTEGER; ext_count INTEGER; BEGIN SELECT COUNT(*) INTO schema_count FROM pg_namespace WHERE nspname IN ('construction', 'estimates', 'infonavit', 'hse', 'hr', 'inventory', 'purchase', 'core', 'core_shared'); SELECT COUNT(*) INTO ext_count FROM pg_extension WHERE extname IN ('uuid-ossp', 'pgcrypto', 'postgis', 'pg_trgm', 'btree_gist'); RAISE NOTICE '============================================================'; RAISE NOTICE 'ERP CONSTRUCCION - Base de datos inicializada'; RAISE NOTICE '============================================================'; RAISE NOTICE 'Extensiones instaladas: %', ext_count; RAISE NOTICE 'Schemas principales creados: %', schema_count; RAISE NOTICE '============================================================'; RAISE NOTICE 'Schemas principales: construction, estimates, infonavit, hse'; RAISE NOTICE 'Schemas extension: hr, inventory, purchase'; RAISE NOTICE 'Schemas compartidos: core, core_shared'; RAISE NOTICE '============================================================'; END $$; -- ============================================================================ -- FIN -- ============================================================================