Contexto y Motivación del Sistema
La gestión manual de los procesos de reclutamiento y búsqueda de empleo en entornos universitarios enfrenta limitaciones significativas ante el gran volumen de datos. Los métodos tradicionales no solo consumen una cantidad excesiva de tiempo, sino que también presentan una alta tasa de errores, dificultan la modificación de registros y complican las tareas de búsqueda y filtrado. Para resolver estas ineficiencias, es fundamental implementar una plataforma digital que estandarice y automatice el flujo de información.
Un sistema centralizado de empleo universitario permite administrar datos de manera eficiente, superando las barreras de la gestión convencional. Al utilizar tecnologías modernas de desarrollo de software, la plataforma facilita operaciones críticas como la inserción, actualización, consulta y análisis estadístico de datos. Esto no solo optimiza el tiempo de los administradores, sino que también garantiza la integridad, confiabilidad y calidad de la información procesada, transformando tareas complejas en procesos ágiles y sistemáticos.
Diseño de la Arquitectura Funcional
Para garantizar la escalabilidad y el mantenimiento del código, la arquitectura del sistema se divide en módulos funcionales específicos. Esta segmentación permite un desarrollo paralelo y una integración continua más fluida, asegurando que cada componente (como la gestión de usuarios, publicación de vacantes, aplicación a ofertas y panel administrativo) opere de manera independiente pero cohesiva dentro del ecosistema general.
Configuración del Backend: Interecptores y Recursos Estáticos
En el lado del servidor, es crucial manejar la autenticación y la autorización de las rutas, así como servir correctamente los activos estáticos. A continuación, se presenta una configuración moderna utilizando WebMvcConfigurer en Spring Boot, lo cual evita la anulación innecesaria de las autoconfiguraciones predeterminadas del framework.
package com.campus.recruitment.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.campus.recruitment.security.JwtAuthInterceptor;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Bean
public JwtAuthInterceptor jwtAuthInterceptor() {
return new JwtAuthInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtAuthInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/auth/**", "/static/**");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/assets/**")
.addResourceLocations("classpath:/static/assets/")
.addResourceLocations("classpath:/public/uploads/");
}
}
Desarrollo del Frontend: Gestión del Perfil de Usuario
Para la interfaz de usuario, se utiliza Vue.js junto con la biblioteca de componentes Element UI. El siguiente código implementa el módulo de "Perfil de Usuario", permitiendo la actualización de datos personales. Se ha refactorizado la lógica para utilizar nomenclatura estándar, peticiones RESTful y validaciones asíncronas más limpias.
<template>
<div class="profile-container">
<el-form :model="userProfile" ref="profileForm" label-width="120px" class="profile-form">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="Nombre Completo" prop="fullName">
<el-input v-model="userProfile.fullName" placeholder="Ingrese su nombre" clearable></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="Teléfono" prop="phoneNumber">
<el-input v-model="userProfile.phoneNumber" placeholder="Número de contacto" clearable></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="Documento ID" prop="idNumber">
<el-input v-model="userProfile.idNumber" placeholder="Número de identificación" clearable></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="Correo Electrónico" prop="emailAddress">
<el-input v-model="userProfile.emailAddress" placeholder="correo@ejemplo.com" clearable></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="Avatar" prop="avatarUrl">
<file-upload
action="/api/files/upload"
:limit="1"
:fileUrls="userProfile.avatarUrl"
@success="handleAvatarSuccess"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="Género" prop="gender">
<el-select v-model="userProfile.gender" placeholder="Seleccione género">
<el-option
v-for="option in genderOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="form-actions">
<el-button type="primary" @click="submitProfileUpdate">Guardar Cambios</el-button>
</el-col>
</el-row>
</el-form>
</div>
</template>
<script>
import { validateEmail, validatePhone } from '@/utils/validators';
export default {
name: 'UserProfile',
data() {
return {
userProfile: {
fullName: '',
phoneNumber: '',
idNumber: '',
emailAddress: '',
avatarUrl: '',
gender: ''
},
genderOptions: []
};
},
created() {
this.fetchUserData();
this.fetchGenderOptions();
},
methods: {
async fetchUserData() {
try {
const response = await this.$http.get('/api/users/profile');
if (response.data.code === 200) {
this.userProfile = response.data.payload;
}
} catch (error) {
this.$message.error('Error al cargar el perfil');
}
},
async fetchGenderOptions() {
try {
const response = await this.$http.get('/api/dictionaries/gender');
this.genderOptions = response.data.payload;
} catch (error) {
console.error('Error fetching dictionaries');
}
},
handleAvatarSuccess(url) {
this.userProfile.avatarUrl = url;
},
submitProfileUpdate() {
if (!this.userProfile.fullName) {
return this.$message.warning('El nombre es obligatorio');
}
if (this.userProfile.phoneNumber && !validatePhone(this.userProfile.phoneNumber)) {
return this.$message.warning('Formato de teléfono inválido');
}
if (this.userProfile.emailAddress && !validateEmail(this.userProfile.emailAddress)) {
return this.$message.warning('Formato de correo inválido');
}
this.$http.put('/api/users/profile', this.userProfile)
.then(res => {
if (res.data.code === 200) {
this.$message.success('Perfil actualizado correctamente');
}
})
.catch(() => this.$message.error('Error al actualizar'));
}
}
};
</script>
<style scoped>
.profile-container {
padding: 20px;
}
.form-actions {
text-align: center;
margin-top: 20px;
}
</style>