-- ===================================================== -- SCHEMA: hr (EXTENSION) -- PROPOSITO: Extensiones de RRHH para construccion -- MODULOS: MAI-007 (RRHH y Asistencias) -- FECHA: 2025-11-24 -- TIPO: Extension del ERP Generico (MGN-010) -- ===================================================== -- NOTA: Este archivo contiene SOLO las extensiones especificas -- de construccion. Las tablas base estan en el ERP Generico. -- ===================================================== -- TYPES (ENUMs) ADICIONALES -- ===================================================== CREATE TYPE hr.attendance_type_construction AS ENUM ( 'manual', -- Captura manual 'biometric', -- Biometrico 'gps', -- GPS desde app movil 'qr_code' -- Codigo QR ); CREATE TYPE hr.worker_type AS ENUM ( 'permanent', -- Planta 'temporary', -- Temporal 'subcontract', -- Subcontratado 'piece_rate' -- Destajista ); CREATE TYPE hr.destajo_status AS ENUM ( 'pending', -- Pendiente medicion 'measured', -- Medido 'approved', -- Aprobado 'paid' -- Pagado ); -- ===================================================== -- TABLES - EXTENSIONES CONSTRUCCION -- ===================================================== -- Tabla: employee_construction (extension de empleados) -- Extiende: hr.employees CREATE TABLE hr.employee_construction ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, employee_id UUID NOT NULL, -- FK a hr.employees (ERP Generico) worker_type hr.worker_type NOT NULL DEFAULT 'permanent', subcontratista_id UUID REFERENCES construction.subcontratistas(id), -- Seguro Social nss VARCHAR(15), imss_registration_date DATE, salary_type VARCHAR(20), -- fijo, mixto, variable -- Capacitacion stps_dc3_number VARCHAR(50), -- Constancia DC-3 stps_dc3_expiry DATE, safety_training_date DATE, first_aid_training_date DATE, -- Documentos has_medical_exam BOOLEAN DEFAULT FALSE, medical_exam_date DATE, has_ine BOOLEAN DEFAULT FALSE, has_curp BOOLEAN DEFAULT FALSE, has_rfc BOOLEAN DEFAULT FALSE, -- Asignacion default_fraccionamiento_id UUID REFERENCES construction.fraccionamientos(id), specialty VARCHAR(100), -- Especialidad: alba~nil, electricista, plomero -- Auditoria created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES auth.users(id), updated_at TIMESTAMP, updated_by UUID REFERENCES auth.users(id), deleted_at TIMESTAMP, deleted_by UUID REFERENCES auth.users(id), CONSTRAINT uq_employee_construction_employee_id UNIQUE (employee_id) ); -- Tabla: asistencias (registro de asistencia) CREATE TABLE hr.asistencias ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, employee_id UUID NOT NULL, -- FK a hr.employees (ERP Generico) fraccionamiento_id UUID NOT NULL REFERENCES construction.fraccionamientos(id), attendance_date DATE NOT NULL, attendance_type hr.attendance_type_construction NOT NULL DEFAULT 'manual', -- Horarios check_in_time TIMESTAMP, check_out_time TIMESTAMP, worked_hours DECIMAL(4,2) GENERATED ALWAYS AS ( CASE WHEN check_out_time IS NOT NULL AND check_in_time IS NOT NULL THEN EXTRACT(EPOCH FROM (check_out_time - check_in_time)) / 3600 ELSE NULL END ) STORED, overtime_hours DECIMAL(4,2) DEFAULT 0, -- GPS check_in_location GEOMETRY(POINT, 4326), check_out_location GEOMETRY(POINT, 4326), check_in_accuracy DECIMAL(8,2), -- Precision en metros check_out_accuracy DECIMAL(8,2), -- Validacion is_valid BOOLEAN DEFAULT TRUE, validation_notes TEXT, validated_by UUID REFERENCES auth.users(id), validated_at TIMESTAMP, -- Auditoria created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES auth.users(id), updated_at TIMESTAMP, updated_by UUID REFERENCES auth.users(id), deleted_at TIMESTAMP, deleted_by UUID REFERENCES auth.users(id), CONSTRAINT uq_asistencias_employee_date UNIQUE (employee_id, attendance_date) ); -- Tabla: asistencia_biometrico (registros biometricos) CREATE TABLE hr.asistencia_biometrico ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, asistencia_id UUID NOT NULL REFERENCES hr.asistencias(id) ON DELETE CASCADE, device_id VARCHAR(50), -- ID del dispositivo biometrico device_name VARCHAR(100), biometric_type VARCHAR(20), -- fingerprint, face, iris verification_score DECIMAL(5,2), -- Score de verificacion 0-100 raw_data TEXT, -- Datos crudos del dispositivo -- Auditoria created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES auth.users(id) ); -- Tabla: geocercas (geocercas para validacion GPS) CREATE TABLE hr.geocercas ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, fraccionamiento_id UUID NOT NULL REFERENCES construction.fraccionamientos(id), name VARCHAR(100) NOT NULL, polygon GEOMETRY(POLYGON, 4326) NOT NULL, radius_meters DECIMAL(8,2), -- Radio de tolerancia is_active BOOLEAN NOT NULL DEFAULT TRUE, -- Auditoria created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES auth.users(id), updated_at TIMESTAMP, updated_by UUID REFERENCES auth.users(id), deleted_at TIMESTAMP, deleted_by UUID REFERENCES auth.users(id) ); -- Tabla: destajo (trabajo a destajo) CREATE TABLE hr.destajo ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, employee_id UUID NOT NULL, -- FK a hr.employees fraccionamiento_id UUID NOT NULL REFERENCES construction.fraccionamientos(id), lote_id UUID REFERENCES construction.lotes(id), departamento_id UUID REFERENCES construction.departamentos(id), concepto_id UUID NOT NULL REFERENCES construction.conceptos(id), period_start DATE NOT NULL, period_end DATE NOT NULL, status hr.destajo_status NOT NULL DEFAULT 'pending', -- Cantidades quantity_measured DECIMAL(12,4) DEFAULT 0, unit_price DECIMAL(12,4) NOT NULL, total_amount DECIMAL(14,2) GENERATED ALWAYS AS (quantity_measured * unit_price) STORED, -- Medicion measured_by UUID REFERENCES auth.users(id), measured_at TIMESTAMP, -- Aprobacion approved_by UUID REFERENCES auth.users(id), approved_at TIMESTAMP, -- Pago payroll_id UUID, -- FK a nomina paid_at TIMESTAMP, notes TEXT, -- Auditoria created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES auth.users(id), updated_at TIMESTAMP, updated_by UUID REFERENCES auth.users(id), deleted_at TIMESTAMP, deleted_by UUID REFERENCES auth.users(id) ); -- Tabla: destajo_detalle (detalle de mediciones) CREATE TABLE hr.destajo_detalle ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, destajo_id UUID NOT NULL REFERENCES hr.destajo(id) ON DELETE CASCADE, description VARCHAR(255) NOT NULL, quantity DECIMAL(12,4) NOT NULL, formula TEXT, -- Formula de calculo photo_url VARCHAR(500), -- Auditoria created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES auth.users(id), updated_at TIMESTAMP, updated_by UUID REFERENCES auth.users(id) ); -- Tabla: cuadrilla (cuadrillas de trabajo) CREATE TABLE hr.cuadrillas ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, fraccionamiento_id UUID NOT NULL REFERENCES construction.fraccionamientos(id), code VARCHAR(20) NOT NULL, name VARCHAR(100) NOT NULL, leader_id UUID, -- FK a hr.employees (lider de cuadrilla) specialty VARCHAR(100), is_active BOOLEAN NOT NULL DEFAULT TRUE, -- Auditoria created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES auth.users(id), updated_at TIMESTAMP, updated_by UUID REFERENCES auth.users(id), deleted_at TIMESTAMP, deleted_by UUID REFERENCES auth.users(id), CONSTRAINT uq_cuadrillas_code_fracc UNIQUE (fraccionamiento_id, code) ); -- Tabla: cuadrilla_miembros (miembros de cuadrilla) CREATE TABLE hr.cuadrilla_miembros ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, cuadrilla_id UUID NOT NULL REFERENCES hr.cuadrillas(id) ON DELETE CASCADE, employee_id UUID NOT NULL, -- FK a hr.employees role VARCHAR(50), -- lider, oficial, ayudante start_date DATE NOT NULL, end_date DATE, is_active BOOLEAN NOT NULL DEFAULT TRUE, -- Auditoria created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES auth.users(id), updated_at TIMESTAMP, updated_by UUID REFERENCES auth.users(id), CONSTRAINT uq_cuadrilla_employee UNIQUE (cuadrilla_id, employee_id, start_date) ); -- ===================================================== -- INDICES -- ===================================================== -- Employee Construction CREATE INDEX idx_employee_construction_tenant_id ON hr.employee_construction(tenant_id); CREATE INDEX idx_employee_construction_employee_id ON hr.employee_construction(employee_id); CREATE INDEX idx_employee_construction_worker_type ON hr.employee_construction(worker_type); CREATE INDEX idx_employee_construction_subcontratista_id ON hr.employee_construction(subcontratista_id); -- Asistencias CREATE INDEX idx_asistencias_tenant_id ON hr.asistencias(tenant_id); CREATE INDEX idx_asistencias_employee_id ON hr.asistencias(employee_id); CREATE INDEX idx_asistencias_fraccionamiento_id ON hr.asistencias(fraccionamiento_id); CREATE INDEX idx_asistencias_date ON hr.asistencias(attendance_date); CREATE INDEX idx_asistencias_type ON hr.asistencias(attendance_type); -- Asistencia Biometrico CREATE INDEX idx_asist_biometrico_tenant_id ON hr.asistencia_biometrico(tenant_id); CREATE INDEX idx_asist_biometrico_asistencia_id ON hr.asistencia_biometrico(asistencia_id); -- Geocercas CREATE INDEX idx_geocercas_tenant_id ON hr.geocercas(tenant_id); CREATE INDEX idx_geocercas_fraccionamiento_id ON hr.geocercas(fraccionamiento_id); CREATE INDEX idx_geocercas_polygon ON hr.geocercas USING GIST(polygon); -- Destajo CREATE INDEX idx_destajo_tenant_id ON hr.destajo(tenant_id); CREATE INDEX idx_destajo_employee_id ON hr.destajo(employee_id); CREATE INDEX idx_destajo_fraccionamiento_id ON hr.destajo(fraccionamiento_id); CREATE INDEX idx_destajo_lote_id ON hr.destajo(lote_id); CREATE INDEX idx_destajo_concepto_id ON hr.destajo(concepto_id); CREATE INDEX idx_destajo_status ON hr.destajo(status); CREATE INDEX idx_destajo_period ON hr.destajo(period_start, period_end); -- Destajo Detalle CREATE INDEX idx_destajo_detalle_tenant_id ON hr.destajo_detalle(tenant_id); CREATE INDEX idx_destajo_detalle_destajo_id ON hr.destajo_detalle(destajo_id); -- Cuadrillas CREATE INDEX idx_cuadrillas_tenant_id ON hr.cuadrillas(tenant_id); CREATE INDEX idx_cuadrillas_fraccionamiento_id ON hr.cuadrillas(fraccionamiento_id); CREATE INDEX idx_cuadrillas_leader_id ON hr.cuadrillas(leader_id); -- Cuadrilla Miembros CREATE INDEX idx_cuadrilla_miembros_tenant_id ON hr.cuadrilla_miembros(tenant_id); CREATE INDEX idx_cuadrilla_miembros_cuadrilla_id ON hr.cuadrilla_miembros(cuadrilla_id); CREATE INDEX idx_cuadrilla_miembros_employee_id ON hr.cuadrilla_miembros(employee_id); -- ===================================================== -- ROW LEVEL SECURITY (RLS) -- ===================================================== ALTER TABLE hr.employee_construction ENABLE ROW LEVEL SECURITY; ALTER TABLE hr.asistencias ENABLE ROW LEVEL SECURITY; ALTER TABLE hr.asistencia_biometrico ENABLE ROW LEVEL SECURITY; ALTER TABLE hr.geocercas ENABLE ROW LEVEL SECURITY; ALTER TABLE hr.destajo ENABLE ROW LEVEL SECURITY; ALTER TABLE hr.destajo_detalle ENABLE ROW LEVEL SECURITY; ALTER TABLE hr.cuadrillas ENABLE ROW LEVEL SECURITY; ALTER TABLE hr.cuadrilla_miembros ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation_employee_construction ON hr.employee_construction USING (tenant_id = get_current_tenant_id()); CREATE POLICY tenant_isolation_asistencias ON hr.asistencias USING (tenant_id = get_current_tenant_id()); CREATE POLICY tenant_isolation_asist_biometrico ON hr.asistencia_biometrico USING (tenant_id = get_current_tenant_id()); CREATE POLICY tenant_isolation_geocercas ON hr.geocercas USING (tenant_id = get_current_tenant_id()); CREATE POLICY tenant_isolation_destajo ON hr.destajo USING (tenant_id = get_current_tenant_id()); CREATE POLICY tenant_isolation_destajo_detalle ON hr.destajo_detalle USING (tenant_id = get_current_tenant_id()); CREATE POLICY tenant_isolation_cuadrillas ON hr.cuadrillas USING (tenant_id = get_current_tenant_id()); CREATE POLICY tenant_isolation_cuadrilla_miembros ON hr.cuadrilla_miembros USING (tenant_id = get_current_tenant_id()); -- ===================================================== -- FUNCTIONS -- ===================================================== -- Funcion: validar asistencia dentro de geocerca CREATE OR REPLACE FUNCTION hr.validate_attendance_location( p_location GEOMETRY, p_fraccionamiento_id UUID ) RETURNS BOOLEAN AS $$ DECLARE v_is_valid BOOLEAN; BEGIN SELECT EXISTS ( SELECT 1 FROM hr.geocercas g WHERE g.fraccionamiento_id = p_fraccionamiento_id AND g.is_active = TRUE AND g.deleted_at IS NULL AND ( ST_Contains(g.polygon, p_location) OR ST_DWithin(g.polygon::geography, p_location::geography, COALESCE(g.radius_meters, 50)) ) ) INTO v_is_valid; RETURN v_is_valid; END; $$ LANGUAGE plpgsql STABLE; COMMENT ON FUNCTION hr.validate_attendance_location IS 'Valida si una ubicacion esta dentro de las geocercas del fraccionamiento'; -- ===================================================== -- COMENTARIOS -- ===================================================== COMMENT ON TABLE hr.employee_construction IS 'Extension: datos adicionales de empleados para construccion'; COMMENT ON TABLE hr.asistencias IS 'Extension: registro de asistencia con GPS/biometrico'; COMMENT ON TABLE hr.asistencia_biometrico IS 'Extension: detalles de registro biometrico'; COMMENT ON TABLE hr.geocercas IS 'Extension: geocercas para validacion de ubicacion'; COMMENT ON TABLE hr.destajo IS 'Extension: trabajo a destajo/precio unitario'; COMMENT ON TABLE hr.destajo_detalle IS 'Extension: detalle de mediciones de destajo'; COMMENT ON TABLE hr.cuadrillas IS 'Extension: cuadrillas de trabajo'; COMMENT ON TABLE hr.cuadrilla_miembros IS 'Extension: miembros de cuadrillas'; -- ===================================================== -- FIN DE EXTENSIONES HR -- =====================================================