iteracion 2
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from decimal import Decimal
|
||||
import uuid
|
||||
from datetime import date, timedelta
|
||||
|
||||
class SpecialistCategoryEnum(models.TextChoices):
|
||||
CAT_1_ELECTRICIDAD = '1', '1. Electricidad de Potencia e Infraestructura'
|
||||
CAT_2_AUTOMATIZACION = '2', '2. Automatización y Control Industrial'
|
||||
CAT_3_DOMOTICA = '3', '3. Domótica e Inmótica (BMS & Climatización)'
|
||||
CAT_4_ELECTRONICA = '4', '4. Electrónica y Hardware (PCB / Firmware)'
|
||||
CAT_5_REDES = '5', '5. Redes, Conectividad e Informática (TI & Ciberseguridad)'
|
||||
CAT_6_INMERSIVAS = '6', '6. Tecnologías Inmersivas y Gemelos Digitales (VR/AR/LiDAR)'
|
||||
CAT_7_SOFTWARE_DATOS_IA_CLOUD = '7', '7. Software, Datos, IA y Cloud'
|
||||
|
||||
class SeniorityEnum(models.TextChoices):
|
||||
JUNIOR = 'JUNIOR', 'Junior (1-3 años)'
|
||||
SEMI_SENIOR = 'SEMI_SENIOR', 'Semi-Senior (3-5 años)'
|
||||
SENIOR = 'SENIOR', 'Senior (5-8 años)'
|
||||
LEAD_SPECIALIST = 'LEAD_SPECIALIST', 'Lead / Principal Specialist (+8 años)'
|
||||
|
||||
class TRLChoiceEnum(models.IntegerChoices):
|
||||
TRL_1 = 1, 'TRL 1 - Principios básicos observados'
|
||||
TRL_2 = 2, 'TRL 2 - Concepto tecnológico formulado'
|
||||
TRL_3 = 3, 'TRL 3 - Prueba de concepto experimental'
|
||||
TRL_4 = 4, 'TRL 4 - Validación en laboratorio'
|
||||
TRL_5 = 5, 'TRL 5 - Validación en entorno relevante'
|
||||
TRL_6 = 6, 'TRL 6 - Demostración en entorno relevante'
|
||||
TRL_7 = 7, 'TRL 7 - Demostración de prototipo en entorno operacional'
|
||||
TRL_8 = 8, 'TRL 8 - Sistema completo y calificado'
|
||||
TRL_9 = 9, 'TRL 9 - Sistema probado con éxito en entorno real/industrial'
|
||||
|
||||
class CandidateStatusEnum(models.TextChoices):
|
||||
POSTULANTE = 'POSTULANTE', 'Postulante Inicial'
|
||||
EN_EVALUACION = 'EN_EVALUACION', 'En Proceso de Evaluación 7D'
|
||||
APROBADO_CARTERA = 'APROBADO_CARTERA', 'Aprobado en Cartera de Especialistas'
|
||||
EN_RESERVA = 'EN_RESERVA', 'En Reserva / Banco de Talento'
|
||||
DESCARTADO = 'DESCARTADO', 'Descartado / No Apto'
|
||||
|
||||
class AvailabilityEnum(models.TextChoices):
|
||||
INMEDIATA = 'INMEDIATA', 'Disponibilidad Inmediata Full-Time'
|
||||
DOS_SEMANAS = 'DOS_SEMANAS', '2 Semanas (Plazo Aviso)'
|
||||
UN_MES = 'UN_MES', '1 Mes'
|
||||
FREELANCE_PROYECTO = 'FREELANCE_PROYECTO', 'Por Horas / Proyectos Específicos'
|
||||
|
||||
|
||||
class SpecialistCandidate(models.Model):
|
||||
"""
|
||||
Ficha de Especialista y Postulante para Cartera I+D+i EFINTEL.
|
||||
"""
|
||||
rut = models.CharField(max_length=20, unique=True, verbose_name="RUT Especialista")
|
||||
first_name = models.CharField(max_length=100, verbose_name="Nombres")
|
||||
last_name = models.CharField(max_length=100, verbose_name="Apellidos")
|
||||
email = models.EmailField(verbose_name="Correo Electrónico")
|
||||
phone = models.CharField(max_length=30, blank=True, verbose_name="Teléfono / WhatsApp")
|
||||
city = models.CharField(max_length=100, default="Santiago", verbose_name="Ciudad / Región de Residencia")
|
||||
|
||||
# Categorización Técnica
|
||||
category_code = models.CharField(max_length=10, choices=SpecialistCategoryEnum.choices, verbose_name="Partida / Categoría Principal (1 a 7)")
|
||||
specialty_title = models.CharField(max_length=150, verbose_name="Título / Especialidad Técnica Específica")
|
||||
seniority = models.CharField(max_length=30, choices=SeniorityEnum.choices, default=SeniorityEnum.SEMI_SENIOR, verbose_name="Nivel de Seniority")
|
||||
trl_level = models.IntegerField(choices=TRLChoiceEnum.choices, default=TRLChoiceEnum.TRL_6, verbose_name="Nivel de Madurez Tecnológica Dominado (TRL 1-9)")
|
||||
|
||||
# Portafolio y Enlaces
|
||||
portfolio_url = models.URLField(blank=True, verbose_name="URL Portafolio / Web Personal")
|
||||
github_url = models.URLField(blank=True, verbose_name="URL GitHub / Repositorio")
|
||||
linkedin_url = models.URLField(blank=True, verbose_name="URL Perfil LinkedIn")
|
||||
|
||||
# Condiciones
|
||||
hourly_rate_clp = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name="Tarifa por Hora Pactada (CLP)")
|
||||
monthly_salary_target_clp = models.DecimalField(max_digits=12, decimal_places=2, default=0, verbose_name="Pretensión de Renta Mensual (CLP)")
|
||||
availability = models.CharField(max_length=30, choices=AvailabilityEnum.choices, default=AvailabilityEnum.INMEDIATA, verbose_name="Disponibilidad")
|
||||
|
||||
status = models.CharField(max_length=30, choices=CandidateStatusEnum.choices, default=CandidateStatusEnum.POSTULANTE, verbose_name="Estado en Cartera")
|
||||
summary = models.TextField(blank=True, verbose_name="Resumen de Experiencia y Competencias")
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.rut} - {self.first_name} {self.last_name} ({self.get_category_code_display()})"
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
return f"{self.first_name} {self.last_name}"
|
||||
|
||||
@property
|
||||
def latest_evaluation(self):
|
||||
return self.evaluations.order_by('-evaluation_date', '-created_at').first()
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Especialista / Candidato"
|
||||
verbose_name_plural = "Cartera de Especialistas"
|
||||
ordering = ['category_code', 'last_name', 'first_name']
|
||||
|
||||
|
||||
class EvaluationRubric7D(models.Model):
|
||||
"""
|
||||
Rúbrica de Evaluación Holística en 7 Dimensiones Ponderadas.
|
||||
Ponderaciones:
|
||||
1. Caso Práctico / Prueba Técnica: 25% (CRITERIO KNOCKOUT: < 70 Descarte Automático)
|
||||
2. Portafolio y Proyectos Previos: 15%
|
||||
3. Competencias Técnicas Específicas: 15%
|
||||
4. Competencias Cardinales / Blandas: 15%
|
||||
5. Experiencia Comprobable en Terreno: 10%
|
||||
6. Alineación con Principios y Seguridad: 10%
|
||||
7. Capacidad de Innovación e I+D+i: 10%
|
||||
Vigencia: 12 meses.
|
||||
"""
|
||||
candidate = models.ForeignKey(SpecialistCandidate, on_delete=models.CASCADE, related_name='evaluations', verbose_name="Especialista Candidato")
|
||||
evaluator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, verbose_name="Evaluador Líder")
|
||||
evaluation_date = models.DateField(default=date.today, verbose_name="Fecha de Evaluación")
|
||||
|
||||
# 7 Dimensiones (Escala 0 - 100)
|
||||
dim_case_study = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="1. Caso Práctico / Prueba Técnica (25% - Knockout < 70)")
|
||||
dim_portfolio = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="2. Portafolio y Proyectos Previos (15%)")
|
||||
dim_tech_specific = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="3. Competencias Técnicas Específicas (15%)")
|
||||
dim_cardinal_skills = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="4. Competencias Cardinales / Blandas (15%)")
|
||||
dim_experience = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="5. Experiencia en Terreno / Industria (10%)")
|
||||
dim_principles_safety = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="6. Alineación con Principios y Seguridad EFINTEL (10%)")
|
||||
dim_innovation_rndi = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="7. Capacidad de Innovación e I+D+i (10%)")
|
||||
|
||||
# Flags y Resultado
|
||||
is_knockout = models.BooleanField(default=False, verbose_name="Descarte por Criterio Knockout (Prueba < 70)")
|
||||
total_weighted_score = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="Puntaje Ponderado Total (0-100)")
|
||||
result_status = models.CharField(max_length=30, choices=CandidateStatusEnum.choices, verbose_name="Dictamen Final")
|
||||
|
||||
valid_until = models.DateField(verbose_name="Vigencia de la Calificación (12 Meses)")
|
||||
feedback = models.TextField(blank=True, verbose_name="Observaciones, Fortalezas y Brechas Detectadas")
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# Auto-set validity to 12 months from evaluation date if not set
|
||||
if not self.valid_until and self.evaluation_date:
|
||||
self.valid_until = self.evaluation_date + timedelta(days=365)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f"Evaluación 7D {self.candidate.full_name} - Score: {self.total_weighted_score}/100 ({self.get_result_status_display()})"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Evaluación 7D de Especialista"
|
||||
verbose_name_plural = "Evaluaciones Rúbrica 7D"
|
||||
ordering = ['-evaluation_date', '-created_at']
|
||||
Reference in New Issue
Block a user