34849-vm/hr_reclutamiento.php
2026-06-24 21:44:23 +00:00

605 lines
30 KiB
PHP

<?php
session_start();
$userRole = $_SESSION['user_role'] ?? '';
if (!isset($_SESSION['user_id']) || ($userRole !== 'Administrador' && $userRole !== 'admin')) {
header('Location: dashboard.php');
exit;
}
require_once 'db/config.php';
$pdo = db();
$schemaInitError = null;
require_once 'includes/hr_bootstrap.php';
try {
hr_ensure_schema($pdo);
} catch (Throwable $e) {
$schemaInitError = $e->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';
?>
<div class="container-fluid mt-4">
<?php if ($notice): ?>
<div class="alert alert-<?= htmlspecialchars($noticeType, ENT_QUOTES, 'UTF-8'); ?> mb-4" role="alert">
<?= $notice; ?>
</div>
<?php endif; ?>
<div class="row g-4">
<!-- NUEVO POSTULANTE (arriba, ancho completo) -->
<div class="col-12">
<div class="card shadow-sm mb-2">
<div class="card-header bg-light">
<h2 class="h5 mb-0">Nuevo Postulante</h2>
</div>
<div class="card-body">
<form method="POST" enctype="multipart/form-data">
<input type="hidden" name="action" value="add_reclutamiento">
<div class="row g-3">
<div class="col-lg-2 col-md-3">
<label class="form-label">Fecha de Registro</label>
<input type="date" name="fecha_registro" class="form-control" value="<?= htmlspecialchars($fechaHoy, ENT_QUOTES, 'UTF-8'); ?>" required>
</div>
<div class="col-lg-4 col-md-6">
<label class="form-label">Edad, Hijos/Estudia Trabaja</label>
<input type="text" name="edad_hijos_estudia_trabaja" class="form-control" placeholder="Ej: 28 años, 1 hijo, trabaja" >
</div>
<div class="col-lg-2 col-md-3">
<label class="form-label">OCUPACION</label>
<input type="text" name="organizacion_personal" class="form-control" maxlength="50" placeholder="Ej: CARRERA/ESTUDIANTE" >
</div>
<div class="col-lg-2 col-md-3">
<label class="form-label">Estado</label>
<select name="estado" class="form-select" required>
<?php foreach ($estadosValidos as $e): ?>
<option value="<?= htmlspecialchars($e, ENT_QUOTES, 'UTF-8'); ?>" <?= $e === 'Postulación Recibida' ? 'selected' : ''; ?>>
<?= htmlspecialchars($e, ENT_QUOTES, 'UTF-8'); ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="row g-3 mt-1">
<div class="col-lg-2 col-md-4">
<label class="form-label">Claridad en Expresarse</label>
<input type="text" name="claridad_expresarse" class="form-control" maxlength="50" placeholder="Ej: A/B/C" >
</div>
<div class="col-lg-2 col-md-4">
<label class="form-label">Manejo de Objeciones</label>
<input type="text" name="manejo_objeciones" class="form-control" maxlength="50" placeholder="Ej: A/B/C" >
</div>
<div class="col-lg-2 col-md-4">
<label class="form-label">EXPERIENCIA LABORAL</label>
<input type="text" name="experiencia_otros_trabajos" class="form-control" maxlength="50" placeholder="Ej: VENTAS" >
</div>
<div class="col-lg-3 col-md-6">
<label class="form-label">RESPONSABILIDAD/COMPROMISO</label>
<input type="text" name="experiencia_relevante_ventas" class="form-control" maxlength="50" placeholder="Ej: ORGANIZACION" >
</div>
<div class="col-lg-3 col-md-6">
<label class="form-label">DISPONIBILIDAD INMEDIATA</label>
<input type="text" name="empatia_trato" class="form-control" maxlength="50" placeholder="Ej: EQUIPOS/INMEDIATES" >
</div>
</div>
<hr class="my-4">
<div class="row g-3">
<div class="col-lg-4 col-md-6">
<label class="form-label">Nombre Completo</label>
<input type="text" name="nombre_completo" class="form-control" required>
</div>
<div class="col-lg-2 col-md-3">
<label class="form-label">DNI (Opcional)</label>
<input type="text" name="dni" class="form-control" maxlength="20" placeholder="Opcional">
</div>
<div class="col-lg-3 col-md-6">
<label class="form-label">Celular</label>
<input type="text" name="celular" class="form-control" required>
</div>
<div class="col-lg-3 col-md-6">
<label class="form-label">Correo (Opcional)</label>
<input type="email" name="correo" class="form-control" maxlength="255" placeholder="tu@email.com">
</div>
</div>
<div class="row g-3">
<div class="col-lg-4 col-md-6">
<label class="form-label">Ciudad</label>
<input type="text" name="ciudad" class="form-control" required>
</div>
<div class="col-lg-4 col-md-6">
<label class="form-label">Puesto Postulado</label>
<input type="text" name="puesto_postulado" class="form-control" required>
</div>
<div class="col-lg-4 col-md-12">
<label class="form-label">CV (PDF)</label>
<input type="file" name="cv_pdf" class="form-control" accept="application/pdf" required>
<div class="form-text">Sube el PDF del CV.</div>
</div>
</div>
<div class="row g-3">
<div class="col-lg-6 col-md-12">
<label class="form-label">Portafolio (Opcional)</label>
<input type="file" name="portafolio" class="form-control" accept="application/pdf,.doc,.docx">
<div class="form-text">PDF o documento Word (si aplica).</div>
</div>
<div class="col-lg-6 col-md-12">
<label class="form-label">Observaciones</label>
<textarea name="observaciones" class="form-control" rows="3"></textarea>
</div>
</div>
<button type="submit" class="btn btn-primary w-100 mt-3">
<i class="fas fa-save me-1"></i> Guardar
</button>
</form>
</div>
</div>
</div>
<!-- FILTROS + TABLA (ancho completo) -->
<div class="col-12">
<div class="card shadow-sm mb-4">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<h2 class="h5 mb-0">Postulantes</h2>
<span class="badge bg-secondary">Total: <?= count($candidatos); ?></span>
</div>
<div class="card-body">
<form method="GET" class="row g-2 align-items-end">
<div class="col-sm-5">
<label class="form-label">Puesto</label>
<input type="text" name="puesto" class="form-control" placeholder="Ej: Ventas" value="<?= htmlspecialchars($filter_puesto, ENT_QUOTES, 'UTF-8'); ?>">
</div>
<div class="col-sm-3">
<label class="form-label">Estado</label>
<select name="estado" class="form-select">
<option value="Todos">Todos</option>
<?php foreach ($estadosValidos as $e): ?>
<option value="<?= htmlspecialchars($e, ENT_QUOTES, 'UTF-8'); ?>" <?= $filter_estado === $e ? 'selected' : ''; ?>>
<?= htmlspecialchars($e, ENT_QUOTES, 'UTF-8'); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-sm-2">
<label class="form-label">Desde</label>
<input type="date" name="fecha_desde" class="form-control" value="<?= htmlspecialchars($fecha_desde, ENT_QUOTES, 'UTF-8'); ?>">
</div>
<div class="col-sm-2">
<label class="form-label">Hasta</label>
<input type="date" name="fecha_hasta" class="form-control" value="<?= htmlspecialchars($fecha_hasta, ENT_QUOTES, 'UTF-8'); ?>">
</div>
<div class="col-12 d-flex justify-content-end gap-2 mt-2">
<a class="btn btn-outline-secondary" href="hr_reclutamiento.php">Limpiar</a>
<button class="btn btn-primary" type="submit">
<i class="fas fa-filter me-1"></i> Filtrar
</button>
</div>
</form>
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered table-striped align-middle">
<thead class="table-light">
<tr>
<th>ID</th>
<th>Fecha Registro</th>
<th>Edad, Hijos/Estudia Trabaja</th>
<th>OCUPACION</th>
<th>Claridad en Expresarse</th>
<th>Manejo de Objeciones</th>
<th>EXPERIENCIA LABORAL</th>
<th>RESPONSABILIDAD/COMPROMISO</th>
<th>DISPONIBILIDAD INMEDIATA</th>
<th>Nombre Completo</th>
<th>DNI</th>
<th>Celular</th>
<th>Correo</th>
<th>Ciudad</th>
<th>Puesto</th>
<th>Estado</th>
<th>CV</th>
<th>Portafolio</th>
<th>Observaciones</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php if (empty($candidatos)): ?>
<tr>
<td colspan="20" class="text-center py-4 text-muted">No hay postulantes para los filtros seleccionados.</td>
</tr>
<?php else: ?>
<?php foreach ($candidatos as $c): ?>
<tr>
<td><?= (int)$c['id']; ?></td>
<td><?= htmlspecialchars($c['fecha_registro'] ?? '', ENT_QUOTES, 'UTF-8'); ?></td>
<td>
<?php $edad = $c['edad_hijos_estudia_trabaja'] ?? null; ?>
<?php if ($edad === null || $edad === ''): ?>
<span class="text-muted">—</span>
<?php else: ?>
<div style="white-space: pre-wrap; word-break: break-word; max-width: 240px;"><?= htmlspecialchars($edad, ENT_QUOTES, 'UTF-8'); ?></div>
<?php endif; ?>
</td>
<td>
<?php $v = $c['organizacion_personal'] ?? null; ?>
<?php if ($v === null || $v === ''): ?>
<span class="text-muted">—</span>
<?php else: ?>
<span class="badge bg-primary text-white"><?= htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8'); ?></span>
<?php endif; ?>
</td>
<td>
<?php $v = $c['claridad_expresarse'] ?? null; ?>
<?php if ($v === null || $v === ''): ?>
<span class="text-muted">—</span>
<?php else: ?>
<span class="badge bg-primary text-white"><?= htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8'); ?></span>
<?php endif; ?>
</td>
<td>
<?php $v = $c['manejo_objeciones'] ?? null; ?>
<?php if ($v === null || $v === ''): ?>
<span class="text-muted">—</span>
<?php else: ?>
<span class="badge bg-primary text-white"><?= htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8'); ?></span>
<?php endif; ?>
</td>
<td>
<?php $v = $c['experiencia_otros_trabajos'] ?? null; ?>
<?php if ($v === null || $v === ''): ?>
<span class="text-muted">—</span>
<?php else: ?>
<span class="badge bg-primary text-white"><?= htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8'); ?></span>
<?php endif; ?>
</td>
<td>
<?php $v = $c['experiencia_relevante_ventas'] ?? null; ?>
<?php if ($v === null || $v === ''): ?>
<span class="text-muted">—</span>
<?php else: ?>
<span class="badge bg-primary text-white"><?= htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8'); ?></span>
<?php endif; ?>
</td>
<td>
<?php $v = $c['empatia_trato'] ?? null; ?>
<?php if ($v === null || $v === ''): ?>
<span class="text-muted">—</span>
<?php else: ?>
<span class="badge bg-primary text-white"><?= htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8'); ?></span>
<?php endif; ?>
</td>
<td><?= htmlspecialchars($c['nombre_completo'] ?? '', ENT_QUOTES, 'UTF-8'); ?></td>
<td><?= htmlspecialchars($c['dni'] ?? '', ENT_QUOTES, 'UTF-8'); ?></td>
<td><?= htmlspecialchars($c['celular'] ?? '', ENT_QUOTES, 'UTF-8'); ?></td>
<td><?= htmlspecialchars($c['correo'] ?? '', ENT_QUOTES, 'UTF-8'); ?></td>
<td><?= htmlspecialchars($c['ciudad'] ?? '', ENT_QUOTES, 'UTF-8'); ?></td>
<td><?= htmlspecialchars($c['puesto_postulado'] ?? '', ENT_QUOTES, 'UTF-8'); ?></td>
<td>
<span class="badge bg-info text-dark"><?= htmlspecialchars($c['estado'] ?? '', ENT_QUOTES, 'UTF-8'); ?></span>
</td>
<td>
<?php if (!empty($c['cv_pdf_path'])): ?>
<a href="<?= htmlspecialchars($c['cv_pdf_path'], ENT_QUOTES, 'UTF-8'); ?>" download class="btn btn-sm btn-outline-primary">
<i class="fas fa-file-pdf"></i>
</a>
<?php else: ?>
<span class="text-muted">—</span>
<?php endif; ?>
</td>
<td>
<?php if (!empty($c['portafolio_path'])): ?>
<a href="<?= htmlspecialchars($c['portafolio_path'], ENT_QUOTES, 'UTF-8'); ?>" download class="btn btn-sm btn-outline-secondary">
<i class="fas fa-file"></i>
</a>
<?php else: ?>
<span class="text-muted">—</span>
<?php endif; ?>
</td>
<td style="max-width: 220px;">
<div style="white-space: pre-wrap;"><?= htmlspecialchars($c['observaciones'] ?? '', ENT_QUOTES, 'UTF-8'); ?></div>
</td>
<td>
<form method="POST" class="d-flex flex-column gap-1">
<input type="hidden" name="action" value="update_reclutamiento_estado">
<input type="hidden" name="id" value="<?= (int)$c['id']; ?>">
<select name="estado" class="form-select form-select-sm">
<?php foreach ($estadosValidos as $e): ?>
<option value="<?= htmlspecialchars($e, ENT_QUOTES, 'UTF-8'); ?>" <?= ($c['estado'] ?? '') === $e ? 'selected' : ''; ?>>
<?= htmlspecialchars($e, ENT_QUOTES, 'UTF-8'); ?>
</option>
<?php endforeach; ?>
</select>
<button type="submit" class="btn btn-sm btn-success">
<i class="fas fa-save me-1"></i> Guardar
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?php include 'layout_footer.php'; ?>