-- ============================================================================ -- 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), CONSTRAINT uq_employees_codigo UNIQUE (tenant_id, codigo), CONSTRAINT uq_employees_curp UNIQUE (tenant_id, curp) ); -- 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); -- 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_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 -- ============================================================================