Rev 523 | Rev 525 | Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?phpdeclare(strict_types=1);namespace LeadersLinked\Controller;use Smalot\PdfParser\Parser;use Laminas\Mvc\Controller\AbstractActionController;use Laminas\View\Model\ViewModel;use Laminas\View\Model\JsonModel;use Laminas\Db\Adapter\AdapterInterface;use Laminas\Db\Sql\Select;use Laminas\Db\Adapter\Adapter;use Laminas\Db\Sql\Sql;use Laminas\Db\ResultSet\HydratingResultSet;use Laminas\Hydrator\ArraySerializableHydrator;use LeadersLinked\Mapper\CompetencyMapper;use LeadersLinked\Mapper\JobDescriptionMapper;use LeadersLinked\Mapper\JobDescriptionCompetencyMapper;use LeadersLinked\Mapper\LocationMapper;use LeadersLinked\Mapper\QueryMapper;use ArrayObject;class RecruitmentCreateJobDescriptionController extends AbstractActionController{/**** @var \Laminas\Db\Adapter\AdapterInterface*/private $adapter;/**** @var \LeadersLinked\Cache\CacheInterface*/private $cache;/**** @var \Laminas\Log\LoggerInterface*/private $logger;/**** @var array*/private $config;/**** @var \Laminas\Mvc\I18n\Translator*/private $translator;/**** @param \Laminas\Db\Adapter\AdapterInterface $adapter* @param \LeadersLinked\Cache\CacheInterface $cache* @param \Laminas\Log\LoggerInterface LoggerInterface $logger* @param array $config* @param \Laminas\Mvc\I18n\Translator $translator*/public function __construct($adapter, $cache, $logger, $config, $translator){$this->adapter = $adapter;$this->cache = $cache;$this->logger = $logger;$this->config = $config;$this->translator = $translator;}public function indexAction(){$request = $this->getRequest();// 🔹 Obtener el ID desde la ruta (URL) con el formato /endpoint/:id$jobDescriptionId = $this->params()->fromRoute('id');// 🔹 Verificar si el ID es válidoif (!$jobDescriptionId) {return new JsonModel(['success' => false,'message' => 'Missing vacancy ID']);}// 🔹 Obtener usuario y permisos ACL$currentUserPlugin = $this->plugin('currentUserPlugin');$currentUser = $currentUserPlugin->getUser();$acl = $this->getEvent()->getViewModel()->getVariable('acl');// 🔹 Verificar si el usuario tiene permiso para extraer criteriosif (!$acl->isAllowed($currentUser->usertype_id, 'recruitment-ai/job-description')) {return new JsonModel(['success' => false,'message' => 'Access denied']);}if ($request->isGet()) {// Llamar a la función que obtiene los archivos de la vacante$data = $this->analyzeJobDescriptionCompetencies($jobDescriptionId);return new JsonModel(['success' => true,'data' => $data]);}// Si el método no es GET ni POSTreturn new JsonModel(['success' => false,'message' => 'Invalid request method']);}public function getJobDescriptionCompetencies($jobDescriptionId){// 🔹 Validación básica del IDif (!is_numeric($jobDescriptionId) || $jobDescriptionId <= 0) {return null;}try {// 🔹 Crear el QueryMapper$queryMapper = QueryMapper::getInstance($this->adapter);$select = $queryMapper->getSql()->select();// 🔹 FROM JobDescriptionMapper$select->from(['jd' => JobDescriptionMapper::_TABLE]);$select->columns(['name','functions','objectives']);// 🔹 JOIN con JobDescriptionCompetencyMapper$select->join(['jdc' => JobDescriptionCompetencyMapper::_TABLE],'jd.id = jdc.job_description_id',[] // No seleccionamos nada directamente de esta tabla);// 🔹 JOIN con CompetencyMapper$select->join(['c' => CompetencyMapper::_TABLE],'jdc.competency_id = c.id',['competency_name' => 'name','competency_description' => 'description']);// 🔹 WHERE por ID de descripción de trabajo$select->where->equalTo('jd.id', $jobDescriptionId);// 🔹 Ejecutar la consulta$statement = $queryMapper->getSql()->prepareStatementForSqlObject($select);$resultSet = $statement->execute();// 🔹 Procesar resultados$hydrator = new ArraySerializableHydrator();$hydratingResultSet = new HydratingResultSet($hydrator);$hydratingResultSet->initialize($resultSet);// 🔹 Agrupar resultados por competencias$jobInfo = null;$competencies = [];foreach ($hydratingResultSet as $row) {if (!$jobInfo) {$jobInfo = ['name' => $row['name'],'functions' => $row['functions'],'objectives' => $row['objectives']];}$competencies[] = ['name' => $row['competency_name'],'description' => $row['competency_description']];}if (!$jobInfo) {return null;}// 🔹 Construir el texto formateado$formattedText ="### Información del Trabajo\n" ."Nombre: {$jobInfo['name']}\n" ."Funciones: {$jobInfo['functions']}\n" ."Objetivos: {$jobInfo['objectives']}\n\n" ."### Competencias requeridas:\n";foreach ($competencies as $index => $comp) {$formattedText .= ($index + 1) . ". {$comp['name']} - {$comp['description']}\n";}return $formattedText;} catch (\Exception $e) {// Aquí podrías loggear el error si es necesarioreturn null;}}function cleanContent($text){// Eliminar la palabra "json"$text = str_replace("json", "", $text);// Eliminar los saltos de línea \n$text = str_replace("\n", "", $text);// Eliminar los acentos invertidos (```)$text = str_replace("```", "", $text);// Retornar el contenido arregladoreturn $text;}public function analyzeJobDescriptionCompetencies($jobDescriptionId){// 🔹 Obtener texto de la descripción de cargo y competencias$descriptionText = $this->getJobDescriptionCompetencies($jobDescriptionId);if (!$descriptionText) {return ['success' => false,'message' => 'No se encontró la descripción del cargo.','data' => null];}// 🔹 Crear el mensaje para OpenAI$messages = [['role' => 'system','content' => "Eres un experto en talento humano, análisis de perfiles laborales y gestión de competencias parael mercado laboral actual."],['role' => 'user','content' => "A continuación te proporciono una descripción de un cargo con sus funciones, objetivos y competencias requeridas.Analiza si las competencias listadas están actualizadas para los requerimientos actuales de la industria y el trabajo moderno.Si es necesario añade nuevas competencias y cambia la descripción de las que se te proprcionaron en base al puesto.Retorna una respuesta estructurada en formato JSON como este:{\"is_updated\": true o false,\"list_competencies\": [{\"name\": \"nombre de la competencia\",\"description\": \"recomendación o análisis de si está o no actualizada, o si falta complementar\"}]}Texto a analizar:\"\"\"$descriptionText\"\"\""]];// 🔹 Consultar OpenAI$response = $this->analyzeCvWithAi($messages);// 🔹 Validar y retornarif (!isset($response)) {return ['success' => false,'message' => 'Error al consultar la API de OpenAI','data' => $response['message'] ?? null];}// 🔹 Intentar extraer JSON del mensaje generado$reply = $this->cleanContent($response['choices'][0]['message']['content'] ?? '{}');if (!$reply) {return ['success' => false,'message' => 'No se obtuvo una respuesta válida de la IA.','data' => null];}// 🔹 Intentar decodificar respuesta JSON (por si OpenAI responde directamente con JSON)$decoded = json_decode($reply, true);if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {return $decoded;}// 🔹 Si no fue posible decodificar, devolver contenido bruto para revisiónreturn ['success' => false,'message' => 'No se pudo decodificar el JSON de la respuesta','data' => $reply];}function callExternalApi($url, $payload, $headers){$ch = curl_init($url);curl_setopt($ch, CURLOPT_CAINFO, '/etc/apache2/ssl/cacert.pem');curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);curl_setopt($ch, CURLOPT_POST, true);curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);$response = curl_exec($ch);$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);$curlError = curl_error($ch);curl_close($ch);// Verificar si hubo un error en la peticiónif ($curlError) {error_log("cURL Error: " . $curlError);return [false, "cURL Error: " . $curlError];}// Si la API devuelve un código de error (4xx o 5xx), registrarloif ($httpCode >= 400) {error_log("Error HTTP {$httpCode}: " . $response);return [false, "Error HTTP {$httpCode}: " . $response];}// Intentar decodificar la respuesta JSON$decodedResponse = json_decode($response, true);// Verificar si la respuesta es válidaif (json_last_error() !== JSON_ERROR_NONE) {error_log("Error al decodificar JSON: " . json_last_error_msg());return [false, "Error al decodificar JSON: " . json_last_error_msg()];}return $decodedResponse;}function analyzeCvWithAi(array $messages){$apiKey = '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';$url = "https://api.openai.com/v1/chat/completions";$payload = json_encode(['model' => 'gpt-4o-mini','messages' => $messages,'temperature' => 0.7]);$headers = ['Content-Type: application/json',"Authorization: Bearer $apiKey"];return $this->callExternalApi($url, $payload, $headers);}}