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']