Rev 521 | Rev 523 | Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?php
declare(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álido
if (!$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 criterios
if (!$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
$competencies = $this->analyzeJobDescriptionCompetencies($jobDescriptionId);
return new JsonModel([
'success' => true,
'data' => $competencies
]);
}
// Si el método no es GET ni POST
return new JsonModel([
'success' => false,
'message' => 'Invalid request method'
]);
}
public function getJobDescriptionCompetencies($jobDescriptionId)
{
// 🔹 Validación básica del ID
if (!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 necesario
return 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 arreglado
return $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 para
el 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.
Retorna una respuesta estructurada en formato JSON como este:
{
\"actualizado\": true o false,
\"competencias\": [
{
\"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 retornar
if (!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 [
'success' => true,
'message' => 'Análisis completado',
'data' => $decoded
];
}
// 🔹 Si no fue posible decodificar, devolver contenido bruto para revisión
return [
'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ón
if ($curlError) {
error_log("cURL Error: " . $curlError);
return [false, "cURL Error: " . $curlError];
}
// Si la API devuelve un código de error (4xx o 5xx), registrarlo
if ($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álida
if (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);
}
}