Files
dtic-DIIAA/servicios/bitacoras/docker/init.sql
T

373 lines
19 KiB
PL/PgSQL

-- Inicialización de base de datos PostgreSQL para DTIC Bitácoras
-- Ejecutado automáticamente al crear el contenedor
-- Crear esquema principal
CREATE SCHEMA IF NOT EXISTS dtic;
-- Usar el esquema dtic por defecto
SET search_path TO dtic;
-- Tabla de técnicos
CREATE TABLE IF NOT EXISTS tecnicos (
id SERIAL PRIMARY KEY,
dtic_id VARCHAR(20) UNIQUE NOT NULL,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
phone VARCHAR(20),
department VARCHAR(100) NOT NULL,
role VARCHAR(20) NOT NULL CHECK (role IN ('admin', 'technician', 'viewer')),
password_hash VARCHAR(255),
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Índices para búsquedas comunes
CREATE INDEX IF NOT EXISTS idx_tecnicos_email ON tecnicos (email);
CREATE INDEX IF NOT EXISTS idx_tecnicos_department ON tecnicos (department);
CREATE INDEX IF NOT EXISTS idx_tecnicos_role ON tecnicos (role);
CREATE INDEX IF NOT EXISTS idx_tecnicos_active ON tecnicos (is_active);
CREATE INDEX IF NOT EXISTS idx_tecnicos_dtic_id ON tecnicos (dtic_id);
-- Tabla de tareas
CREATE TABLE IF NOT EXISTS tareas (
id SERIAL PRIMARY KEY,
dtic_id VARCHAR(20) UNIQUE NOT NULL,
title VARCHAR(200) NOT NULL,
description TEXT,
technician_id INTEGER REFERENCES tecnicos(id) ON DELETE SET NULL,
status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'in_progress', 'completed', 'cancelled')),
priority VARCHAR(10) DEFAULT 'medium' CHECK (priority IN ('low', 'medium', 'high', 'urgent')),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
due_date TIMESTAMP WITH TIME ZONE,
completed_at TIMESTAMP WITH TIME ZONE
);
-- Índices para tareas
CREATE INDEX IF NOT EXISTS idx_tareas_technician ON tareas (technician_id);
CREATE INDEX IF NOT EXISTS idx_tareas_status ON tareas (status);
CREATE INDEX IF NOT EXISTS idx_tareas_priority ON tareas (priority);
CREATE INDEX IF NOT EXISTS idx_tareas_due_date ON tareas (due_date);
-- Tabla de auditoría
CREATE TABLE IF NOT EXISTS audit_log (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES tecnicos(id) ON DELETE SET NULL,
action VARCHAR(50) NOT NULL,
entity_type VARCHAR(50) NOT NULL,
entity_id INTEGER,
old_values JSONB,
new_values JSONB,
ip_address INET,
user_agent TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Índices para auditoría
CREATE INDEX IF NOT EXISTS idx_audit_user ON audit_log (user_id);
CREATE INDEX IF NOT EXISTS idx_audit_entity ON audit_log (entity_type, entity_id);
CREATE INDEX IF NOT EXISTS idx_audit_created ON audit_log (created_at);
-- Función para generar DTIC ID
CREATE OR REPLACE FUNCTION dtic.generate_dtic_id(prefix TEXT)
RETURNS TEXT AS $$
DECLARE
counter INTEGER;
new_id TEXT;
BEGIN
-- Obtener el último contador para este prefijo desde todas las tablas
SELECT COALESCE(MAX(CAST(SUBSTRING(dtic_id FROM LENGTH(prefix) + 2) AS INTEGER)), 0) + 1
INTO counter
FROM (
SELECT dtic_id FROM dtic.tecnicos WHERE dtic_id LIKE prefix || '-%'
UNION ALL
SELECT dtic_id FROM dtic.tareas WHERE dtic_id LIKE prefix || '-%'
UNION ALL
SELECT dtic_id FROM dtic.recursos WHERE dtic_id LIKE prefix || '-%'
UNION ALL
SELECT dtic_id FROM dtic.usuarios_relacionados WHERE dtic_id LIKE prefix || '-%'
UNION ALL
SELECT dtic_id FROM dtic.tarea_recursos WHERE dtic_id LIKE prefix || '-%'
) AS all_ids;
-- Generar nuevo ID
new_id := prefix || '-' || LPAD(counter::TEXT, 4, '0');
RETURN new_id;
END;
$$ LANGUAGE plpgsql;
-- Función para actualizar updated_at automáticamente
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Triggers para actualizar updated_at
CREATE TRIGGER update_tecnicos_updated_at
BEFORE UPDATE ON tecnicos
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_tareas_updated_at
BEFORE UPDATE ON tareas
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Insertar datos de ejemplo
INSERT INTO tecnicos (dtic_id, first_name, last_name, email, password_hash, department, role, is_active) VALUES
('TEC-0001', 'Juan', 'PÉREZ', 'juan.perez@dtic.gob.ar', '$2a$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewdBPjYQmHqU9yO', 'dtic', 'admin', true),
('TEC-0002', 'María', 'GONZÁLEZ', 'maria.gonzalez@dtic.gob.ar', '$2a$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewdBPjYQmHqU9yO', 'sistemas', 'technician', true),
('TEC-0003', 'Carlos', 'RODRÍGUEZ', 'carlos.rodriguez@dtic.gob.ar', '$2a$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewdBPjYQmHqU9yO', 'redes', 'technician', true),
('TEC-0004', 'Ana', 'MARTÍNEZ', 'ana.martinez@dtic.gob.ar', '$2a$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewdBPjYQmHqU9yO', 'seguridad', 'technician', true),
('TEC-0005', 'Luis', 'LÓPEZ', 'luis.lopez@dtic.gob.ar', '$2a$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewdBPjYQmHqU9yO', 'dtic', 'viewer', true),
('TEC-0006', 'Laura', 'SÁNCHEZ', 'laura.sanchez@dtic.gob.ar', '$2a$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewdBPjYQmHqU9yO', 'sistemas', 'technician', false),
('TEC-0007', 'Rodrigo', 'MONLA', 'rmonla@frlr.utn.edu.ar', '$2a$12$T4OKj7qWBnjtYhXGmDibP.WszYyFIF5iKEZW3TDpVE2GI9Pfmu5zS', 'dtic', 'admin', true)
ON CONFLICT (dtic_id) DO NOTHING;
-- Tabla de recursos
CREATE TABLE IF NOT EXISTS recursos (
id SERIAL PRIMARY KEY,
dtic_id VARCHAR(20) UNIQUE NOT NULL,
name VARCHAR(200) NOT NULL,
description TEXT,
category VARCHAR(50) NOT NULL CHECK (category IN ('hardware', 'software', 'network', 'security', 'tools', 'facilities')),
status VARCHAR(20) DEFAULT 'available' CHECK (status IN ('available', 'assigned', 'maintenance', 'retired')),
location VARCHAR(200),
technical_specs JSONB,
serial_number VARCHAR(100),
model VARCHAR(100),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Índices para recursos
CREATE INDEX IF NOT EXISTS idx_recursos_dtic_id ON recursos (dtic_id);
CREATE INDEX IF NOT EXISTS idx_recursos_category ON recursos (category);
CREATE INDEX IF NOT EXISTS idx_recursos_status ON recursos (status);
CREATE INDEX IF NOT EXISTS idx_recursos_location ON recursos (location);
-- Tabla de usuarios relacionados (diferentes de técnicos del sistema)
CREATE TABLE IF NOT EXISTS usuarios_relacionados (
id SERIAL PRIMARY KEY,
dtic_id VARCHAR(20) UNIQUE NOT NULL,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE,
phone VARCHAR(20),
department VARCHAR(100),
position VARCHAR(100),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Índices para usuarios relacionados
CREATE INDEX IF NOT EXISTS idx_usuarios_relacionados_dtic_id ON usuarios_relacionados (dtic_id);
CREATE INDEX IF NOT EXISTS idx_usuarios_relacionados_email ON usuarios_relacionados (email);
CREATE INDEX IF NOT EXISTS idx_usuarios_relacionados_department ON usuarios_relacionados (department);
-- Tabla de asignaciones recurso-usuario (relación muchos a muchos)
CREATE TABLE IF NOT EXISTS recurso_asignaciones (
id SERIAL PRIMARY KEY,
recurso_id INTEGER NOT NULL REFERENCES recursos(id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES usuarios_relacionados(id) ON DELETE CASCADE,
assigned_by INTEGER REFERENCES tecnicos(id), -- técnico que realizó la asignación
assigned_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
unassigned_by INTEGER REFERENCES tecnicos(id), -- técnico que realizó la desasignación
unassigned_at TIMESTAMP WITH TIME ZONE,
activo BOOLEAN DEFAULT true,
-- Constraint único para asignaciones activas
UNIQUE (recurso_id, user_id)
);
-- Índices para asignaciones de recursos
CREATE INDEX IF NOT EXISTS idx_recurso_asignaciones_recurso ON recurso_asignaciones (recurso_id);
CREATE INDEX IF NOT EXISTS idx_recurso_asignaciones_user ON recurso_asignaciones (user_id);
CREATE INDEX IF NOT EXISTS idx_recurso_asignaciones_activo ON recurso_asignaciones (activo);
CREATE INDEX IF NOT EXISTS idx_recurso_asignaciones_assigned_at ON recurso_asignaciones (assigned_at);
-- Tabla de asignaciones tarea-recurso (relación muchos a muchos)
CREATE TABLE IF NOT EXISTS tarea_recursos (
id SERIAL PRIMARY KEY,
tarea_id INTEGER NOT NULL REFERENCES tareas(id) ON DELETE CASCADE,
recurso_id INTEGER NOT NULL REFERENCES recursos(id) ON DELETE CASCADE,
assigned_by INTEGER REFERENCES tecnicos(id), -- técnico que realizó la asignación
assigned_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
unassigned_by INTEGER REFERENCES tecnicos(id), -- técnico que realizó la desasignación
unassigned_at TIMESTAMP WITH TIME ZONE,
activo BOOLEAN DEFAULT true,
-- Campos adicionales específicos de la asignación
estimated_hours DECIMAL(5,2), -- horas estimadas para usar el recurso en esta tarea
actual_hours DECIMAL(5,2), -- horas reales utilizadas
notes TEXT, -- notas específicas sobre el uso del recurso en esta tarea
-- Constraint único para asignaciones activas de la misma tarea-recurso
UNIQUE (tarea_id, recurso_id, activo)
);
-- Tabla de historial de asignaciones tarea-recurso
CREATE TABLE IF NOT EXISTS tarea_recurso_historial (
id SERIAL PRIMARY KEY,
tarea_recurso_id INTEGER NOT NULL REFERENCES tarea_recursos(id) ON DELETE CASCADE,
action VARCHAR(50) NOT NULL, -- 'assigned', 'unassigned', 'updated', 'completed'
tecnico_id INTEGER REFERENCES tecnicos(id), -- técnico que realizó la acción
old_values JSONB, -- valores anteriores
new_values JSONB, -- valores nuevos
details TEXT, -- descripción detallada de la acción
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Tabla de historial de recursos
CREATE TABLE IF NOT EXISTS recurso_historial (
id SERIAL PRIMARY KEY,
recurso_id INTEGER NOT NULL REFERENCES recursos(id) ON DELETE CASCADE,
action VARCHAR(50) NOT NULL, -- 'assigned', 'unassigned', 'maintenance', 'created', 'updated'
usuario_id INTEGER REFERENCES usuarios_relacionados(id), -- usuario asignado (si aplica)
tecnico_id INTEGER REFERENCES tecnicos(id), -- técnico que realizó la acción
details TEXT, -- descripción detallada de la acción
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Índices para asignaciones tarea-recurso
CREATE INDEX IF NOT EXISTS idx_tarea_recursos_tarea ON tarea_recursos (tarea_id);
CREATE INDEX IF NOT EXISTS idx_tarea_recursos_recurso ON tarea_recursos (recurso_id);
CREATE INDEX IF NOT EXISTS idx_tarea_recursos_activo ON tarea_recursos (activo);
CREATE INDEX IF NOT EXISTS idx_tarea_recursos_assigned_at ON tarea_recursos (assigned_at);
-- Índices para historial de asignaciones tarea-recurso
CREATE INDEX IF NOT EXISTS idx_tarea_recurso_historial_tarea_recurso ON tarea_recurso_historial (tarea_recurso_id);
CREATE INDEX IF NOT EXISTS idx_tarea_recurso_historial_action ON tarea_recurso_historial (action);
CREATE INDEX IF NOT EXISTS idx_tarea_recurso_historial_tecnico ON tarea_recurso_historial (tecnico_id);
CREATE INDEX IF NOT EXISTS idx_tarea_recurso_historial_created ON tarea_recurso_historial (created_at);
-- Índices para historial de recursos
CREATE INDEX IF NOT EXISTS idx_recurso_historial_recurso ON recurso_historial (recurso_id);
CREATE INDEX IF NOT EXISTS idx_recurso_historial_action ON recurso_historial (action);
CREATE INDEX IF NOT EXISTS idx_recurso_historial_tecnico ON recurso_historial (tecnico_id);
CREATE INDEX IF NOT EXISTS idx_recurso_historial_created ON recurso_historial (created_at);
-- Triggers para actualizar updated_at
CREATE TRIGGER update_recursos_updated_at
BEFORE UPDATE ON recursos
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_usuarios_relacionados_updated_at
BEFORE UPDATE ON usuarios_relacionados
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Función para validar asignaciones de recursos a tareas
CREATE OR REPLACE FUNCTION validate_tarea_recurso_assignment()
RETURNS TRIGGER AS $$
BEGIN
-- Si es una nueva asignación activa
IF NEW.activo = true THEN
-- Verificar que el recurso esté disponible (no asignado a otras tareas activas)
IF EXISTS (
SELECT 1 FROM tarea_recursos tr
WHERE tr.recurso_id = NEW.recurso_id
AND tr.activo = true
AND tr.id != COALESCE(NEW.id, 0)
) THEN
RAISE EXCEPTION 'El recurso ya está asignado a otra tarea activa';
END IF;
-- Verificar que el recurso esté en estado 'available'
IF NOT EXISTS (
SELECT 1 FROM recursos r
WHERE r.id = NEW.recurso_id
AND r.status = 'available'
) THEN
RAISE EXCEPTION 'El recurso debe estar en estado disponible para ser asignado';
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Trigger para validar asignaciones
CREATE TRIGGER validate_tarea_recurso_assignment_trigger
BEFORE INSERT OR UPDATE ON tarea_recursos
FOR EACH ROW EXECUTE FUNCTION validate_tarea_recurso_assignment();
-- Función para actualizar estado de recursos basado en asignaciones
CREATE OR REPLACE FUNCTION update_recurso_status_from_assignments()
RETURNS TRIGGER AS $$
BEGIN
-- Si se asigna un recurso a una tarea
IF (TG_OP = 'INSERT' AND NEW.activo = true) OR (TG_OP = 'UPDATE' AND NEW.activo = true AND OLD.activo = false) THEN
-- Cambiar estado del recurso a 'assigned'
UPDATE recursos SET status = 'assigned', updated_at = CURRENT_TIMESTAMP
WHERE id = NEW.recurso_id AND status = 'available';
-- Si se desasigna un recurso de una tarea
ELSIF (TG_OP = 'UPDATE' AND NEW.activo = false AND OLD.activo = true) OR TG_OP = 'DELETE' THEN
-- Verificar si el recurso ya no está asignado a ninguna tarea activa
IF NOT EXISTS (
SELECT 1 FROM tarea_recursos tr
WHERE tr.recurso_id = COALESCE(NEW.recurso_id, OLD.recurso_id)
AND tr.activo = true
) THEN
-- Cambiar estado del recurso a 'available'
UPDATE recursos SET status = 'available', updated_at = CURRENT_TIMESTAMP
WHERE id = COALESCE(NEW.recurso_id, OLD.recurso_id) AND status = 'assigned';
END IF;
END IF;
RETURN COALESCE(NEW, OLD);
END;
$$ LANGUAGE plpgsql;
-- Trigger para actualizar estado de recursos
CREATE TRIGGER update_recurso_status_from_assignments_trigger
AFTER INSERT OR UPDATE OR DELETE ON tarea_recursos
FOR EACH ROW EXECUTE FUNCTION update_recurso_status_from_assignments();
-- Insertar datos de ejemplo para recursos
INSERT INTO recursos (dtic_id, name, description, category, status, location, technical_specs, serial_number, model) VALUES
('REC-0001', 'Laptop Dell Latitude 5420', 'Laptop para trabajo administrativo', 'hardware', 'available', 'Oficina Principal', '{"cpu": "Intel i5", "ram": "16GB", "storage": "512GB SSD"}', 'DL5420-2025-001', 'Dell Latitude 5420'),
('REC-0002', 'Proyector Epson EB-S41', 'Proyector para presentaciones', 'hardware', 'assigned', 'Sala de Reuniones', '{"resolution": "SVGA", "brightness": "3200 lumens"}', 'EP-S41-2025-002', 'Epson EB-S41'),
('REC-0003', 'Antivirus Bitdefender Total Security', 'Licencia corporativa de antivirus', 'software', 'available', NULL, '{"licenses": 25, "valid_until": "2025-12-15"}', 'BD-TS-2025-003', 'Bitdefender Total Security'),
('REC-0004', 'Router Cisco RV340', 'Router para red corporativa', 'network', 'maintenance', 'Sala de Servidores', '{"ports": "16 Gigabit", "wifi": "Dual Band"}', 'CS-RV340-2025-004', 'Cisco RV340'),
('REC-0005', 'Kit de Herramientas de Red', 'Herramientas para mantenimiento de red', 'tools', 'available', 'Depósito Técnico', '{"items": ["Tester", "Cables", "Conectores"]}', 'KT-RD-2025-005', 'Kit Herramientas Red'),
('REC-0006', 'Licencia Windows Server 2022', 'Licencia para servidor principal', 'software', 'assigned', 'Sala de Servidores', '{"edition": "Standard", "cores": 16}', 'WS2022-STD-006', 'Windows Server 2022')
ON CONFLICT (dtic_id) DO NOTHING;
-- Insertar datos de ejemplo para usuarios relacionados
INSERT INTO usuarios_relacionados (dtic_id, first_name, last_name, email, phone, department, position) VALUES
('USR-0001', 'Juan', 'Pérez', 'juan.perez@empresa.com', '+54 11 1234-5678', 'Administración', 'Gerente'),
('USR-0002', 'María', 'González', 'maria.gonzalez@empresa.com', '+54 11 2345-6789', 'Recursos Humanos', 'Coordinadora'),
('USR-0003', 'Carlos', 'Rodríguez', 'carlos.rodriguez@empresa.com', '+54 11 3456-7890', 'IT', 'Analista'),
('USR-0004', 'Ana', 'Martínez', 'ana.martinez@empresa.com', '+54 11 4567-8901', 'Contabilidad', 'Contadora'),
('USR-0005', 'Luis', 'López', 'luis.lopez@empresa.com', '+54 11 5678-9012', 'Ventas', 'Vendedor')
ON CONFLICT (dtic_id) DO NOTHING;
-- Insertar asignaciones de ejemplo
INSERT INTO recurso_asignaciones (recurso_id, user_id, assigned_by) VALUES
(2, 1, 1), -- Proyector asignado a Juan Pérez
(6, 3, 2) -- Windows Server asignado a Carlos Rodríguez
ON CONFLICT DO NOTHING;
-- Crear índices adicionales para búsquedas de texto completo
CREATE INDEX IF NOT EXISTS idx_tecnicos_full_name ON tecnicos USING gin (to_tsvector('spanish', first_name || ' ' || last_name));
CREATE INDEX IF NOT EXISTS idx_tecnicos_search ON tecnicos USING gin (to_tsvector('spanish', first_name || ' ' || last_name || ' ' || email || ' ' || dtic_id));
CREATE INDEX IF NOT EXISTS idx_recursos_name ON recursos USING gin (to_tsvector('spanish', name || ' ' || description || ' ' || dtic_id));
CREATE INDEX IF NOT EXISTS idx_usuarios_relacionados_name ON usuarios_relacionados USING gin (to_tsvector('spanish', first_name || ' ' || last_name || ' ' || email || ' ' || dtic_id));
-- Comentarios en las tablas
COMMENT ON TABLE tecnicos IS 'Tabla principal de técnicos del sistema DTIC Bitácoras';
COMMENT ON TABLE tareas IS 'Tabla de tareas asignadas a técnicos';
COMMENT ON TABLE tarea_recursos IS 'Tabla de relación many-to-many entre tareas y recursos asignados';
COMMENT ON TABLE tarea_recurso_historial IS 'Historial de asignaciones y desasignaciones de recursos a tareas';
COMMENT ON TABLE audit_log IS 'Registro de auditoría de todas las operaciones del sistema';
-- Permisos básicos (ajustar según necesidades de seguridad)
GRANT USAGE ON SCHEMA dtic TO dtic_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA dtic TO dtic_user;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA dtic TO dtic_user;