erp-construccion-database-v2/schemas/02-hr-schema-ddl.sql
Adrian Flores Cortes 094cbe3ffb [EPIC-002] feat: Add user_id FK to employees + rename warehouse_type
T2.1: Renamed warehouse_type -> construction_warehouse_type
      in almacenes_proyecto to avoid naming conflict with ERP-Core

T2.3: Added user_id column to hr.employees
      - Optional FK to auth.users (employee may not have system access)
      - Unique constraint for 1:1 mapping
      - Conditional FK (only created if auth.users exists)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 00:20:02 -06:00

175 lines
7.0 KiB
SQL

-- ============================================================================
-- HR Schema DDL - Extension de RRHH para Construccion
-- Modulo: MAI-007 RRHH y Asistencias
-- Version: 1.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.
-- ============================================================================
-- Verificar prerequisitos
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'auth') THEN
RAISE EXCEPTION 'Schema auth no existe. ERP-Core debe estar instalado';
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'auth' AND tablename = 'tenants') THEN
RAISE EXCEPTION 'Tabla auth.tenants no existe. ERP-Core debe estar instalado';
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'construction') THEN
RAISE EXCEPTION 'Schema construction no existe. Ejecutar primero 01-construction-schema-ddl.sql';
END IF;
END $$;
-- Crear schema si no existe
CREATE SCHEMA IF NOT EXISTS hr;
-- Configurar search_path
SET search_path TO hr, construction, core, core_shared, public;
-- ============================================================================
-- TABLAS BASE (requeridas por HSE y otros modulos)
-- ============================================================================
-- Tabla: Empleados
CREATE TABLE IF NOT EXISTS hr.employees (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
codigo VARCHAR(20) NOT NULL,
nombre VARCHAR(100) NOT NULL,
apellido_paterno VARCHAR(100) NOT NULL,
apellido_materno VARCHAR(100),
curp VARCHAR(18),
rfc VARCHAR(13),
nss VARCHAR(11),
fecha_nacimiento DATE,
genero VARCHAR(1),
email VARCHAR(255),
telefono VARCHAR(20),
direccion TEXT,
fecha_ingreso DATE NOT NULL,
fecha_baja DATE,
puesto_id UUID,
departamento VARCHAR(100),
tipo_contrato VARCHAR(50),
salario_diario DECIMAL(10,2),
estado VARCHAR(20) NOT NULL DEFAULT 'activo',
foto_url VARCHAR(500),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
-- Mapeo a usuario del sistema (opcional - empleado puede no tener acceso)
user_id UUID,
CONSTRAINT uq_employees_codigo UNIQUE (tenant_id, codigo),
CONSTRAINT uq_employees_curp UNIQUE (tenant_id, curp),
CONSTRAINT uq_employees_user_id UNIQUE (user_id)
);
-- Tabla: Puestos
CREATE TABLE IF NOT EXISTS hr.puestos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
codigo VARCHAR(20) NOT NULL,
nombre VARCHAR(100) NOT NULL,
descripcion TEXT,
nivel_riesgo VARCHAR(20),
requiere_capacitacion_especial BOOLEAN DEFAULT false,
activo BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_puestos_codigo UNIQUE (tenant_id, codigo)
);
-- Agregar FK de puesto a empleados
ALTER TABLE hr.employees
ADD CONSTRAINT fk_employees_puesto
FOREIGN KEY (puesto_id) REFERENCES hr.puestos(id);
-- FK condicional: employees.user_id → auth.users (si tabla existe)
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'auth' AND tablename = 'users') THEN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_employees_user') THEN
ALTER TABLE hr.employees
ADD CONSTRAINT fk_employees_user
FOREIGN KEY (user_id) REFERENCES auth.users(id)
ON DELETE SET NULL;
RAISE NOTICE 'FK creada: hr.employees.user_id → auth.users';
END IF;
END IF;
END $$;
-- Tabla: Asignacion de empleados a obras
CREATE TABLE IF NOT EXISTS hr.employee_fraccionamientos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
employee_id UUID NOT NULL REFERENCES hr.employees(id),
fraccionamiento_id UUID NOT NULL REFERENCES construction.fraccionamientos(id),
fecha_inicio DATE NOT NULL,
fecha_fin DATE,
rol VARCHAR(50),
activo BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_employee_fraccionamiento UNIQUE (employee_id, fraccionamiento_id, fecha_inicio)
);
-- ============================================================================
-- INDICES
-- ============================================================================
CREATE INDEX IF NOT EXISTS idx_employees_tenant ON hr.employees(tenant_id);
CREATE INDEX IF NOT EXISTS idx_employees_estado ON hr.employees(estado);
CREATE INDEX IF NOT EXISTS idx_employees_puesto ON hr.employees(puesto_id);
CREATE INDEX IF NOT EXISTS idx_employees_user_id ON hr.employees(user_id);
CREATE INDEX IF NOT EXISTS idx_puestos_tenant ON hr.puestos(tenant_id);
CREATE INDEX IF NOT EXISTS idx_employee_fraccionamientos_employee ON hr.employee_fraccionamientos(employee_id);
CREATE INDEX IF NOT EXISTS idx_employee_fraccionamientos_fraccionamiento ON hr.employee_fraccionamientos(fraccionamiento_id);
-- ============================================================================
-- ROW LEVEL SECURITY
-- ============================================================================
ALTER TABLE hr.employees ENABLE ROW LEVEL SECURITY;
ALTER TABLE hr.puestos ENABLE ROW LEVEL SECURITY;
ALTER TABLE hr.employee_fraccionamientos ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_employees ON hr.employees
FOR ALL
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
CREATE POLICY tenant_isolation_puestos ON hr.puestos
FOR ALL
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
CREATE POLICY tenant_isolation_employee_fraccionamientos ON hr.employee_fraccionamientos
FOR ALL
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
-- ============================================================================
-- TRIGGERS
-- ============================================================================
CREATE TRIGGER trg_employees_updated_at
BEFORE UPDATE ON hr.employees
FOR EACH ROW EXECUTE FUNCTION core_shared.set_updated_at();
CREATE TRIGGER trg_puestos_updated_at
BEFORE UPDATE ON hr.puestos
FOR EACH ROW EXECUTE FUNCTION core_shared.set_updated_at();
-- ============================================================================
-- COMENTARIOS
-- ============================================================================
COMMENT ON TABLE hr.employees IS 'Empleados de la empresa';
COMMENT ON TABLE hr.puestos IS 'Catalogo de puestos de trabajo';
COMMENT ON TABLE hr.employee_fraccionamientos IS 'Asignacion de empleados a obras/fraccionamientos';
-- ============================================================================
-- FIN
-- ============================================================================