erp-construccion/docs/04-modelado/database-design/schemas/hr-ext-schema-ddl.sql

395 lines
15 KiB
PL/PgSQL

-- =====================================================
-- 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
-- =====================================================