getMessage(); } $pageTitle = "Recursos Humanos - Reclutamiento"; $pageDescription = "Gestión de postulantes: CV, portafolio, indicadores y estado del proceso de reclutamiento."; $estadosValidos = [ 'Postulación Recibida', 'Entrevista Pendiente', 'Entrevista Realizada', 'Aprobado', 'Contratado', 'No Seleccionado', ]; function hr_move_uploaded_file(array $file, string $uploadDirAbs, array $allowedExts): string { $error = $file['error'] ?? UPLOAD_ERR_NO_FILE; if ($error !== UPLOAD_ERR_OK) { throw new RuntimeException('Error al cargar el archivo.'); } $size = (int)($file['size'] ?? 0); $maxBytes = 8 * 1024 * 1024; if ($size <= 0) { throw new RuntimeException('Archivo vacío.'); } if ($size > $maxBytes) { throw new RuntimeException('El archivo excede el tamaño máximo permitido (8MB).'); } $origName = (string)($file['name'] ?? 'archivo'); $ext = strtolower(pathinfo($origName, PATHINFO_EXTENSION)); if ($ext === '' || !in_array($ext, $allowedExts, true)) { throw new RuntimeException('Tipo de archivo no permitido.'); } if (!is_dir($uploadDirAbs)) { mkdir($uploadDirAbs, 0775, true); } $base = pathinfo($origName, PATHINFO_FILENAME); $base = preg_replace('/[^A-Za-z0-9._-]+/', '_', $base); if (!$base) { $base = 'archivo'; } $newName = time() . '_' . $base . '.' . $ext; $targetAbs = rtrim($uploadDirAbs, '/\\') . DIRECTORY_SEPARATOR . $newName; if (!move_uploaded_file($file['tmp_name'], $targetAbs)) { throw new RuntimeException('No se pudo guardar el archivo en el servidor.'); } $uploadDirRel = str_replace(__DIR__ . DIRECTORY_SEPARATOR, '', rtrim($uploadDirAbs, '/\\')); return $uploadDirRel . '/' . $newName; } $notice = null; $noticeType = 'success'; if (!empty($schemaInitError)) { $notice = 'Error al preparar las tablas de Recursos Humanos.'; $noticeType = 'danger'; error_log('HR schema init error (reclutamiento): ' . $schemaInitError); } if (isset($_GET['success']) && $_GET['success'] === '1') { $notice = 'Postulante guardado correctamente.'; $noticeType = 'success'; } if (isset($_GET['estado_updated']) && $_GET['estado_updated'] === '1') { $notice = 'Estado actualizado correctamente.'; $noticeType = 'success'; } if ($_SERVER['REQUEST_METHOD'] === 'POST') { $action = $_POST['action'] ?? ''; if ($action === 'add_reclutamiento') { try { $fecha_registro = $_POST['fecha_registro'] ?? date('Y-m-d'); $nombre_completo = trim((string)($_POST['nombre_completo'] ?? '')); $dni = trim((string)($_POST['dni'] ?? '')); $celular = trim((string)($_POST['celular'] ?? '')); $correo = trim((string)($_POST['correo'] ?? '')); // Truncamos para evitar errores de longitud en BD (cuando el campo no es obligatorio). $dni = $dni === '' ? '' : substr($dni, 0, 20); $correo = $correo === '' ? '' : substr($correo, 0, 255); $ciudad = trim((string)($_POST['ciudad'] ?? '')); $puesto_postulado = trim((string)($_POST['puesto_postulado'] ?? '')); $estado = (string)($_POST['estado'] ?? 'Postulación Recibida'); $observaciones = trim((string)($_POST['observaciones'] ?? '')); // Indicadores (opcionales) $edad_hijos_estudia_trabaja = trim((string)($_POST['edad_hijos_estudia_trabaja'] ?? '')); if ($edad_hijos_estudia_trabaja === '') { $edad_hijos_estudia_trabaja = null; } // Indicadores (opcionales) - se guardan como LETRAS (A/B/C...). $organizacion_personal = strtoupper(trim((string)($_POST['organizacion_personal'] ?? ''))); $claridad_expresarse = strtoupper(trim((string)($_POST['claridad_expresarse'] ?? ''))); $manejo_objeciones = strtoupper(trim((string)($_POST['manejo_objeciones'] ?? ''))); $experiencia_otros_trabajos = strtoupper(trim((string)($_POST['experiencia_otros_trabajos'] ?? ''))); $experiencia_relevante_ventas = strtoupper(trim((string)($_POST['experiencia_relevante_ventas'] ?? ''))); $empatia_trato = strtoupper(trim((string)($_POST['empatia_trato'] ?? ''))); $organizacion_personal = $organizacion_personal === '' ? null : substr($organizacion_personal, 0, 50); $claridad_expresarse = $claridad_expresarse === '' ? null : substr($claridad_expresarse, 0, 50); $manejo_objeciones = $manejo_objeciones === '' ? null : substr($manejo_objeciones, 0, 50); $experiencia_otros_trabajos = $experiencia_otros_trabajos === '' ? null : substr($experiencia_otros_trabajos, 0, 50); $experiencia_relevante_ventas = $experiencia_relevante_ventas === '' ? null : substr($experiencia_relevante_ventas, 0, 50); $empatia_trato = $empatia_trato === '' ? null : substr($empatia_trato, 0, 50); if ($nombre_completo === '' || $celular === '' || $ciudad === '' || $puesto_postulado === '') { throw new RuntimeException('Faltan campos obligatorios.'); } if ($correo !== '' && !filter_var($correo, FILTER_VALIDATE_EMAIL)) { throw new RuntimeException('Correo inválido.'); } if (!in_array($estado, $estadosValidos, true)) { throw new RuntimeException('Estado inválido.'); } $cvPdfFile = $_FILES['cv_pdf'] ?? null; if (!$cvPdfFile || ($cvPdfFile['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) { throw new RuntimeException('El CV (PDF) es obligatorio.'); } $cvUploadDirAbs = __DIR__ . '/assets/uploads/hr/reclutamiento/cv/'; $cv_pdf_path = hr_move_uploaded_file($cvPdfFile, $cvUploadDirAbs, ['pdf']); $portafolio_path = null; $portFile = $_FILES['portafolio'] ?? null; if ($portFile && !empty($portFile['name']) && ($portFile['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_OK) { $portUploadDirAbs = __DIR__ . '/assets/uploads/hr/reclutamiento/portafolio/'; // Portafolio opcional: permitimos PDF o documentos comunes. $portafolio_path = hr_move_uploaded_file($portFile, $portUploadDirAbs, ['pdf', 'doc', 'docx']); } $stmt = $pdo->prepare('INSERT INTO hr_reclutamiento (fecha_registro, edad_hijos_estudia_trabaja, organizacion_personal, claridad_expresarse, manejo_objeciones, experiencia_otros_trabajos, experiencia_relevante_ventas, empatia_trato, nombre_completo, dni, celular, correo, ciudad, puesto_postulado, estado, observaciones, cv_pdf_path, portafolio_path) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'); $stmt->execute([ $fecha_registro, $edad_hijos_estudia_trabaja, $organizacion_personal, $claridad_expresarse, $manejo_objeciones, $experiencia_otros_trabajos, $experiencia_relevante_ventas, $empatia_trato, $nombre_completo, $dni, $celular, $correo, $ciudad, $puesto_postulado, $estado, $observaciones, $cv_pdf_path, $portafolio_path, ]); header('Location: hr_reclutamiento.php?success=1'); exit; } catch (Throwable $e) { $notice = 'No se pudo guardar el postulante: ' . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8'); $noticeType = 'danger'; } } if ($action === 'update_reclutamiento_estado') { try { $id = (int)($_POST['id'] ?? 0); $nuevoEstado = (string)($_POST['estado'] ?? ''); if ($id <= 0) { throw new RuntimeException('ID inválido.'); } if (!in_array($nuevoEstado, $estadosValidos, true)) { throw new RuntimeException('Estado inválido.'); } $stmt = $pdo->prepare('UPDATE hr_reclutamiento SET estado = ? WHERE id = ?'); $stmt->execute([$nuevoEstado, $id]); header('Location: hr_reclutamiento.php?estado_updated=1'); exit; } catch (Throwable $e) { $notice = 'No se pudo actualizar el estado.'; $noticeType = 'danger'; } } } // --- Filtros --- $filter_puesto = trim((string)($_GET['puesto'] ?? '')); $filter_estado = (string)($_GET['estado'] ?? 'Todos'); $fecha_desde = trim((string)($_GET['fecha_desde'] ?? '')); $fecha_hasta = trim((string)($_GET['fecha_hasta'] ?? '')); if ($filter_estado !== 'Todos' && !in_array($filter_estado, $estadosValidos, true)) { $filter_estado = 'Todos'; } $where = []; $params = []; if ($filter_puesto !== '') { $where[] = 'puesto_postulado LIKE ?'; $params[] = '%' . $filter_puesto . '%'; } if ($filter_estado !== 'Todos') { $where[] = 'estado = ?'; $params[] = $filter_estado; } if ($fecha_desde !== '') { $where[] = 'fecha_registro >= ?'; $params[] = $fecha_desde; } if ($fecha_hasta !== '') { $where[] = 'fecha_registro <= ?'; $params[] = $fecha_hasta; } $sql = 'SELECT id, fecha_registro, edad_hijos_estudia_trabaja, organizacion_personal, claridad_expresarse, manejo_objeciones, experiencia_otros_trabajos, experiencia_relevante_ventas, empatia_trato, nombre_completo, dni, celular, correo, ciudad, puesto_postulado, estado, observaciones, cv_pdf_path, portafolio_path FROM hr_reclutamiento'; if (!empty($where)) { $sql .= ' WHERE ' . implode(' AND ', $where); } $sql .= ' ORDER BY fecha_registro DESC, id DESC'; $candidatos = []; try { $stmt = $pdo->prepare($sql); $stmt->execute($params); $candidatos = $stmt->fetchAll(PDO::FETCH_ASSOC); } catch (PDOException $e) { error_log('HR Reclutamiento DB error: ' . $e->getMessage()); $notice = 'No se pudo cargar la lista de postulantes.'; $noticeType = 'danger'; } $fechaHoy = date('Y-m-d'); include 'layout_header.php'; ?>

Nuevo Postulante


Sube el PDF del CV.
PDF o documento Word (si aplica).

Postulantes

Total:
Limpiar
ID Fecha Registro Edad, Hijos/Estudia Trabaja OCUPACION Claridad en Expresarse Manejo de Objeciones EXPERIENCIA LABORAL RESPONSABILIDAD/COMPROMISO DISPONIBILIDAD INMEDIATA Nombre Completo DNI Celular Correo Ciudad Puesto Estado CV Portafolio Observaciones Acciones
No hay postulantes para los filtros seleccionados.