first commit

This commit is contained in:
Miguel 2025-06-16 11:40:29 -06:00
commit 5d534f9c24
29 changed files with 2461 additions and 0 deletions

476
FormularioInicial.php Normal file
View File

@ -0,0 +1,476 @@
<?php
require_once 'database/database.php';
$query = "SELECT DISTINCT empresa, examen, certificacion FROM entrada";
$result = $conexion->query($query);
$opciones = [];
while ($row = $result->fetch_assoc()) {
$empresa = $row['empresa'];
$examen = $row['examen'];
$certificacion = $row['certificacion'];
if (!isset($opciones[$empresa])) $opciones[$empresa] = [];
if (!isset($opciones[$empresa][$examen])) $opciones[$empresa][$examen] = [];
if (!in_array($certificacion, $opciones[$empresa][$examen])) {
$opciones[$empresa][$examen][] = $certificacion;
}
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Centro de Certificación LANIA</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet">
<link href="style/dashboard.css" rel="stylesheet">
</head>
<body>
<?php include 'sidebar.php'; ?>
<div class="main-content">
<div class="container py-5">
<div class="text-center mb-4">
<h1 class="display-5">Centro de Certificación LANIA</h1>
<p class="lead">Formulario de registro para candidatos de exámenes PEARSON VUE</p>
</div>
<div class="card shadow p-4">
<form id="registroForm" action="model/login.php" method="POST">
<div class="row g-3">
<div class="col-md-6">
<label for="nombre" class="form-label">Nombre completo</label>
<input type="text" class="form-control" id="nombre" name="nombre" required>
</div>
<div class="col-md-6 position-relative">
<label for="correo" class="form-label">Correo electrónico</label>
<input type="email" class="form-control" id="correo" name="correo" required autocomplete="off">
<div id="sugerencias-correo" class="list-group position-absolute w-100" style="z-index: 1000;"></div>
</div>
<div class="col-md-6">
<label for="telefono" class="form-label">Teléfono</label>
<input type="text" class="form-control" id="telefono" name="telefono" required>
</div>
<div class="col-md-6">
<label for="genero" class="form-label">Género</label>
<select class="form-select" id="genero" name="genero" required>
<option value="">Selecciona una opción</option>
<option value="Masculino">Masculino</option>
<option value="Femenino">Femenino</option>
<option value="Otro">Prefiero no especificar</option>
</select>
</div>
<div class="col-md-6">
<label for="edad" class="form-label">Edad</label>
<select class="form-select" id="edad" name="edad" required>
<option value="">Selecciona un rango de edad</option>
<option value="menor">Menos de 17</option>
<option value="18-21">18-21</option>
<option value="22-25">22-25</option>
<option value="26-30">26-30</option>
<option value="31-35">31-35</option>
<option value="36-40">36-40</option>
<option value="41-45">41-45</option>
<option value="46-50">46-50</option>
<option value="51-55">51-55</option>
<option value="56-60">56-60</option>
<option value="mayor">Más de 60</option>
</select>
</div>
<div class="col-md-6">
<label for="estado_procedencia" class="form-label">Estado de procedencia</label>
<input type="text" class="form-control" id="estado_procedencia" name="estado_procedencia" placeholder="Ingresa tu estado de procedencia" required>
</div>
<div class="col-md-4">
<label for="identificacion" class="form-label">Identificación</label>
<select id="identificacion" name="identificacion" class="form-select" required>
<option value="">Seleccione una identificación</option>
<option value="CE">Credencial de Estudiante</option>
<option value="INE">INE</option>
<option value="C.M">Cartilla Militar</option>
<option value="Pasaporte">Pasaporte</option>
</select>
</div>
<div class="col-md-4">
<label for="empresa" class="form-label">Proveedor</label>
<select id="empresa" name="empresa" class="form-select" required onchange="mostrarInputOtro('empresa'); actualizarExamenes();">
<option value="">Seleccione un proveedor</option>
<?php
$proveedoresPredefinidos = ['Microsoft', 'Oracle', 'Cisco'];
foreach ($proveedoresPredefinidos as $proveedor) {
echo "<option value='$proveedor'>$proveedor</option>";
}
foreach ($opciones as $empresa => $examenes) {
if (!in_array($empresa, $proveedoresPredefinidos)) {
echo "<option value='$empresa'>$empresa</option>";
}
}
?>
<option value="otro">Otro</option>
</select>
<input type="text" id="empresa_otro" name="empresa_otro" class="form-control mt-2 d-none" placeholder="Especifica otro proveedor">
</div>
<!-- Examen -->
<div class="col-md-4">
<label for="examen" class="form-label">Examen</label>
<select id="examen" name="examen" class="form-select" required onchange="mostrarInputOtro('examen'); actualizarCertificaciones();">
<option value="">Selecciona un examen</option>
<option value="otro">Otro</option>
</select>
<input type="text" id="examen_otro" name="examen_otro" class="form-control mt-2 d-none" placeholder="Especifica otro examen">
</div>
<!-- Certificación -->
<div class="col-md-4">
<label for="certificacion" class="form-label">Certificación</label>
<select id="certificacion" name="certificacion" class="form-select" required onchange="mostrarInputOtro('certificacion')">
<option value="">Selecciona una certificación</option>
<option value="otro">Otro</option>
</select>
<input type="text" id="certificacion_otro" name="certificacion_otro" class="form-control mt-2 d-none" placeholder="Especifica otra certificación">
</div>
<input type="hidden" id="hora_local" name="h_entrada">
<div class="mt-4 text-end">
<button type="submit" class="btn btn-primary">Enviar registro</button>
</div>
</form>
</div>
</div>
<!-- Modal de confirmación -->
<div class="modal fade" id="modalConfirmacion" tabindex="-1" aria-labelledby="modalConfirmacionLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-success text-white">
<h5 class="modal-title" id="modalConfirmacionLabel">¡Registro exitoso!</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cerrar"></button>
</div>
<div class="modal-body text-center">
<p id="mensajeConfirmacion" class="fs-5 fw-bold">Registro exitoso. Bienvenido a LANIA 😊</p>
</div>
<div class="modal-footer justify-content-center">
<button type="button" class="btn btn-success" data-bs-dismiss="modal">Aceptar</button>
</div>
</div>
</div>
</div>
</div>
<script>
const correoInput = document.getElementById('correo');
const sugerenciasDiv = document.getElementById('sugerencias-correo');
const dominios = ['@gmail.com', '@outlook.com', '@hotmail.com', '@lania.edu.mx'];
correoInput.addEventListener('input', () => {
const valor = correoInput.value.trim();
sugerenciasDiv.innerHTML = '';
if (valor && !valor.includes('@')) {
dominios.forEach(dominio => {
const sugerencia = document.createElement('div');
sugerencia.classList.add('list-group-item', 'list-group-item-action');
sugerencia.textContent = valor + dominio;
sugerencia.onclick = () => {
correoInput.value = sugerencia.textContent;
sugerenciasDiv.innerHTML = '';
};
sugerenciasDiv.appendChild(sugerencia);
});
}
});
document.addEventListener('click', (e) => {
if (!sugerenciasDiv.contains(e.target) && e.target !== correoInput) {
sugerenciasDiv.innerHTML = '';
}
});
const datos = {
Microsoft: {
"AZ-900": ["Fundamentals", "Associate"],
"AZ-104": ["Associate", "Expert"],
"MS-900": ["Fundamentals"]
},
Oracle: {
"Oracle Database SQL": ["Associate", "Professional"],
"Java SE Programmer": ["Associate", "Professional", "Master"]
},
Cisco: {
"CCNA": ["Associate"],
"CCNP": ["Professional"],
"CCIE": ["Expert"]
}
};
const datosBD = <?php echo json_encode($opciones); ?>;
for (const proveedor in datosBD) {
if (!datos[proveedor]) datos[proveedor] = {};
for (const examen in datosBD[proveedor]) {
if (!datos[proveedor][examen]) datos[proveedor][examen] = [];
datosBD[proveedor][examen].forEach(cert => {
if (!datos[proveedor][examen].includes(cert)) {
datos[proveedor][examen].push(cert);
}
});
}
}
function mostrarInputOtro(tipo) {
const select = document.getElementById(tipo);
const inputOtro = document.getElementById(tipo + '_otro');
if (select.value === 'otro') {
inputOtro.classList.remove('d-none');
inputOtro.required = true;
} else {
inputOtro.classList.add('d-none');
inputOtro.value = '';
inputOtro.required = false;
}
// Si cambias proveedor, resetea examen y certificación
if (tipo === 'empresa') {
// Oculta y limpia examen_otro y certificacion_otro
document.getElementById('examen_otro').classList.add('d-none');
document.getElementById('examen_otro').value = '';
document.getElementById('examen_otro').required = false;
document.getElementById('certificacion_otro').classList.add('d-none');
document.getElementById('certificacion_otro').value = '';
document.getElementById('certificacion_otro').required = false;
}
// Si cambias examen, resetea certificación
if (tipo === 'examen') {
document.getElementById('certificacion_otro').classList.add('d-none');
document.getElementById('certificacion_otro').value = '';
document.getElementById('certificacion_otro').required = false;
}
}
function actualizarExamenes() {
const empresa = document.getElementById("empresa").value;
const examenSelect = document.getElementById("examen");
const certificacionSelect = document.getElementById("certificacion");
examenSelect.innerHTML = '<option value="">Selecciona un examen</option>';
if (empresa && datos[empresa]) {
for (let examen in datos[empresa]) {
const option = document.createElement("option");
option.value = examen;
option.textContent = examen;
examenSelect.appendChild(option);
}
}
if (!Array.from(examenSelect.options).some(opt => opt.value === "otro")) {
const optionOtro = document.createElement("option");
optionOtro.value = "otro";
optionOtro.textContent = "Otro";
examenSelect.appendChild(optionOtro);
}
// Reinicia certificaciones
certificacionSelect.innerHTML = '<option value="">Selecciona una certificación</option>';
if (!Array.from(certificacionSelect.options).some(opt => opt.value === "otro")) {
const optionCertOtro = document.createElement("option");
optionCertOtro.value = "otro";
optionCertOtro.textContent = "Otro";
certificacionSelect.appendChild(optionCertOtro);
}
}
function actualizarCertificaciones() {
const empresa = document.getElementById("empresa").value;
const examen = document.getElementById("examen").value;
const certificacionSelect = document.getElementById("certificacion");
certificacionSelect.innerHTML = '<option value="">Selecciona una certificación</option>';
if (empresa && examen && datos[empresa] && datos[empresa][examen]) {
datos[empresa][examen].forEach(cert => {
const option = document.createElement("option");
option.value = cert;
option.textContent = cert;
certificacionSelect.appendChild(option);
});
}
// Solo agrega la opción "Otro" si no existe ya
if (![...certificacionSelect.options].some(opt => opt.value === "otro")) {
const optionOtro = document.createElement("option");
optionOtro.value = "otro";
optionOtro.textContent = "Otro";
certificacionSelect.appendChild(optionOtro);
}
}
document.getElementById("registroForm").addEventListener("submit", function(e) {
const ahora = new Date();
const hora = ahora.toTimeString().slice(0,8); // "HH:MM:SS"
document.getElementById("hora_local").value = hora;
e.preventDefault(); // Evitar el envío predeterminado del formulario
const form = e.target;
const formData = new FormData(form);
const empresaSelect = document.getElementById('empresa');
const empresaOtro = document.getElementById('empresa_otro');
if (empresaSelect.value === 'otro' && empresaOtro.value.trim() !== '') {
formData.set('empresa', empresaOtro.value.trim());
}
const examenSelect = document.getElementById('examen');
const examenOtro = document.getElementById('examen_otro');
if (examenSelect.value === 'otro' && examenOtro.value.trim() !== '') {
formData.set('examen', examenOtro.value.trim());
}
const certSelect = document.getElementById('certificacion');
const certOtro = document.getElementById('certificacion_otro');
if (certSelect.value === 'otro' && certOtro.value.trim() !== '') {
formData.set('certificacion', certOtro.value.trim());
}
if (empresaSelect.value === empresaOtro.value.trim()) {
if (!datos[empresaOtro.value.trim()]) {
datos[empresaOtro.value.trim()] = {};
}
}
if (examenSelect.value === examenOtro.value.trim()) {
if (!datos[empresaSelect.value]) datos[empresaSelect.value] = {};
if (!datos[empresaSelect.value][examenOtro.value.trim()]) {
datos[empresaSelect.value][examenOtro.value.trim()] = [];
}
}
if (certSelect.value === certOtro.value.trim()) {
if (!datos[empresaSelect.value]) datos[empresaSelect.value] = {};
if (!datos[empresaSelect.value][examenSelect.value]) datos[empresaSelect.value][examenSelect.value] = [];
if (!datos[empresaSelect.value][examenSelect.value].includes(certOtro.value.trim())) {
datos[empresaSelect.value][examenSelect.value].push(certOtro.value.trim());
}
}
if (empresaSelect.value === 'otro' && empresaOtro.value.trim() !== '') {
const nuevoProveedor = empresaOtro.value.trim();
if (!datos[nuevoProveedor]) {
datos[nuevoProveedor] = {};
const option = document.createElement("option");
option.value = nuevoProveedor;
option.textContent = nuevoProveedor;
empresaSelect.insertBefore(option, empresaSelect.querySelector('option[value="otro"]'));
}
localStorage.setItem('datosLania', JSON.stringify(datos));
}
if (examenSelect.value === 'otro' && examenOtro.value.trim() !== '') {
const proveedor = empresaSelect.value === 'otro' ? empresaOtro.value.trim() : empresaSelect.value;
const nuevoExamen = examenOtro.value.trim();
if (!datos[proveedor]) datos[proveedor] = {};
if (!datos[proveedor][nuevoExamen]) {
datos[proveedor][nuevoExamen] = [];
// Agregar la opción al select de examen
const option = document.createElement("option");
option.value = nuevoExamen;
option.textContent = nuevoExamen;
examenSelect.insertBefore(option, examenSelect.querySelector('option[value="otro"]'));
}
}
if (certSelect.value === 'otro' && certOtro.value.trim() !== '') {
const proveedor = empresaSelect.value === 'otro' ? empresaOtro.value.trim() : empresaSelect.value;
const examen = examenSelect.value === 'otro' ? examenOtro.value.trim() : examenSelect.value;
const nuevaCert = certOtro.value.trim();
if (!datos[proveedor]) datos[proveedor] = {};
if (!datos[proveedor][examen]) datos[proveedor][examen] = [];
if (!datos[proveedor][examen].includes(nuevaCert)) {
datos[proveedor][examen].push(nuevaCert);
// Agregar la opción al select de certificación
const option = document.createElement("option");
option.value = nuevaCert;
option.textContent = nuevaCert;
certSelect.insertBefore(option, certSelect.querySelector('option[value="otro"]'));
}
}
fetch("model/login.php", {
method: "POST",
body: formData
})
.then(data => {
// Mostrar el modal de confirmación
const modalConfirmacion = new bootstrap.Modal(document.getElementById('modalConfirmacion'));
modalConfirmacion.show();
form.reset(); // Reiniciar el formulario
const examenSelect = document.getElementById("examen");
const certificacionSelect = document.getElementById("certificacion");
examenSelect.innerHTML = '<option value="">Selecciona un examen</option><option value="otro">Otro</option>';
certificacionSelect.innerHTML = '<option value="">Selecciona una certificación</option><option value="otro">Otro</option>';
['empresa', 'examen', 'certificacion'].forEach(function(campo) {
const inputOtro = document.getElementById(campo + '_otro');
if (inputOtro) {
inputOtro.classList.add('d-none');
inputOtro.value = '';
inputOtro.removeAttribute('required');
}
});
})
.catch(error => {
console.error("Error al enviar:", error);
alert("Ocurrió un error al registrar la entrada.");
});
// Guardar datos en localStorage
localStorage.setItem('datosLania', JSON.stringify(datos));
});
const datosGuardados = localStorage.getItem('datosLania');
if (datosGuardados) {
const datosExtra = JSON.parse(datosGuardados);
for (const proveedor in datosExtra) {
if (!datos[proveedor]) datos[proveedor] = {};
for (const examen in datosExtra[proveedor]) {
if (!datos[proveedor][examen]) datos[proveedor][examen] = [];
datosExtra[proveedor][examen].forEach(cert => {
if (!datos[proveedor][examen].includes(cert)) {
datos[proveedor][examen].push(cert);
}
});
}
}
}
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.sidebar-menu li').forEach(item => {
item.classList.remove('active');
});
const menuEntrada = document.getElementById('menu-entrada');
if (menuEntrada) {
menuEntrada.classList.add('active');
}
});
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

164
FormularioSalida.php Normal file
View File

@ -0,0 +1,164 @@
<?php
require_once 'database/database.php';
$query = "SELECT id_usuario, nombre FROM entrada";
$resultado = $conexion->query($query);
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Centro de Certificación LANIA</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome para íconos -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet">
<link href="style/dashboard.css" rel="stylesheet">
</head>
<body class="bg-light">
<?php include 'sidebar.php'; ?>
<div class="main-content">
<div class="container py-5">
<div class="text-center mb-4">
<h1 class="display-5">Centro de Certificación LANIA</h1>
<p class="lead">Formulario de salida para candidatos de exámenes PEARSON VUE</p>
</div>
<div class="card shadow p-4">
<form id="formSalida" method="POST">
<div class="row g-3">
<div class="col-md-6">
<label for="nombre" class="form-label">Nombre del Usuario</label>
<select class="form-select" id="nombre" name="id_usuario" required>
<option value="">Selecciona tu nombre</option>
<?php while ($row = $resultado->fetch_assoc()): ?>
<option value="<?= $row['id_usuario'] ?>"><?= htmlspecialchars($row['nombre']) ?></option>
<?php endwhile; ?>
</select>
</div>
<div class="mt-4 survey">
<h5>Encuesta de satisfacción</h5>
<?php
$preguntas = [
'atencion_personal' => '¿Cómo calificaría usted la atención proporcionada por el personal de LANIA antes y durante la realización del examen?',
'equipo_funcionando' => '¿ El equipo de cómputo proporcionado funcionó correctamente durante todo el examen?',
'ambiente_aula' => '¿Cómo valoraría el ambiente del aula (nivel de ruido, iluminación, temperatura, mobiliario) durante su examen?',
'calidad_internet' => '¿Qué tan satisfactoria fue la calidad de la conexión a internet mientras realizaba su examen?',
'instrucciones_claras' => '¿Recibió instrucciones claras sobre el procedimiento del examen y las normas del centro?',
'respuesta_personal' => 'En caso de surgir dudas o problemas, ¿el personal respondió con rapidez y eficacia? ',
'recomendacion_lania' => '¿Qué tan probable es que usted recomiende el Centro de Exámenes LANIA a otras personas?'
];
foreach ($preguntas as $campo => $texto): ?>
<div class="mb-3">
<label class="form-label"><?= $texto ?></label>
<div>
<?php for ($i = 1; $i <= 5; $i++): ?>
<input
type="radio"
name="<?= $campo ?>"
id="<?= $campo . '_' . $i ?>"
value="<?= $i ?>"
style="display: none;"
>
<label for="<?= $campo . '_' . $i ?>">
<img src="assets/face_<?= $i ?>.png" alt="<?= $i ?>" width="30">
</label>
<?php endfor; ?>
</div>
</div>
<?php endforeach; ?>
</div>
<!-- Consentimiento -->
<input type="hidden" name="consentimiento" value="no">
<div class="form-check mb-4">
<input
class="form-check-input"
type="checkbox"
id="consentimiento"
name="consentimiento"
value="si"
checked
>
<label class="form-check-label" for="consentimiento">
Doy mi consentimiento para recibir publicidad
</label>
</div>
<input type="hidden" id="hora_salida_local" name="hora_salida">
<!-- Botón enviar -->
<div class="mt-4 text-end">
<button type="submit" class="btn btn-primary">Enviar registro</button>
</div>
</form>
</div>
</div>
<!-- Modal de agradecimiento -->
<div class="modal fade" id="modalGracias" tabindex="-1" aria-labelledby="modalGraciasLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-success text-white">
<h5 class="modal-title" id="modalGraciasLabel">¡Salida registrada!</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cerrar"></button>
</div>
<div class="modal-body text-center">
Gracias por tu asistencia.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" data-bs-dismiss="modal">Aceptar</button>
</div>
</div>
</div>
</div>
</div>
<script>
document.getElementById("formSalida").addEventListener("submit", function(e) {
e.preventDefault();
const ahora = new Date();
const hora = ahora.toTimeString().slice(0,8); // "HH:MM:SS"
document.getElementById("hora_salida_local").value = hora;
const form = e.target;
const formData = new FormData(form);
fetch("model/logout.php", {
method: "POST",
body: formData
})
.then(response => response.text())
.then(data => {
const modalGracias = new bootstrap.Modal(document.getElementById('modalGracias'));
modalGracias.show();
form.reset();
})
.catch(error => {
console.error("Error al enviar:", error);
alert("Ocurrió un error al registrar la salida.");
});
});
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.sidebar-menu li').forEach(item => {
item.classList.remove('active');
});
const menuSalida = document.getElementById('menu-salida');
if (menuSalida) {
menuSalida.classList.add('active');
}
});
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

49
LANIA.sql Normal file
View File

@ -0,0 +1,49 @@
use lania;
drop table entrada;
CREATE TABLE entrada(
id_usuario INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(255) NOT NULL,
correo VARCHAR(255) NOT NULL,
telefono VARCHAR(20) NOT NULL,
genero VARCHAR(10) NOT NULL,
edad VARCHAR (50) NOT NULL,
estado_procedencia VARCHAR (50) NOT NULL,
identificacion VARCHAR (50) NOT NULL,
empresa VARCHAR(100) NOT NULL,
examen VARCHAR(50) NOT NULL,
certificacion VARCHAR(50) NOT NULL,
h_entrada TIME NOT NULL,
fecha DATE NOT NULL
);
drop table salida;
CREATE TABLE salida(
id_salida INT AUTO_INCREMENT PRIMARY KEY,
id_usuario INT NOT NULL,
hora_salida TIME NOT NULL,
consentimiento ENUM('si', 'no') DEFAULT 'si',
FOREIGN KEY (id_usuario) REFERENCES entrada(id_usuario)
);
CREATE TABLE encuesta_satisfaccion (
id_encuesta INT AUTO_INCREMENT PRIMARY KEY,
id_salida INT NOT NULL,
atencion_personal TINYINT,
equipo_funcionando TINYINT,
ambiente_aula TINYINT,
calidad_internet TINYINT,
instrucciones_claras TINYINT,
respuesta_personal TINYINT,
recomendacion_lania TINYINT,
FOREIGN KEY (id_salida) REFERENCES salida(id_salida)
);
CREATE TABLE admin (
id_usuario INT AUTO_INCREMENT PRIMARY KEY,
password VARCHAR(255) NOT NULL
);
select * from entrada;
ALTER TABLE salida ADD COLUMN consentimiento VARCHAR(10) DEFAULT 'no';

69
api.js Normal file
View File

@ -0,0 +1,69 @@
const http = require('http');
const mysql = require('mysql2');
// Configuración de la conexión a MySQL
const db = mysql.createConnection({
host: 'localhost', // MySQL está en tu misma máquina
user: 'root', // Usuario por defecto (cámbialo si es necesario)
password: 'Starfox19.', // Asegúrate de que sea la contraseña correcta
database: 'lania', // Nombre de tu base de datos
});
// Conectar a MySQL
db.connect((err) => {
if (err) {
console.error('❌ Error al conectar a MySQL:', err.message);
return;
}
console.log('✅ Conectado a MySQL');
});
// Crear servidor HTTP
const server = http.createServer((req, res) => {
// Solo aceptamos GET en la ruta principal
if (req.method === 'GET' && req.url === '/') {
const query = `
SELECT
e.id_usuario AS user_id,
e.correo AS email,
e.estado_procedencia AS procedencia,
e.edad AS rango_edad,
e.empresa AS proveedor,
s.consentimiento AS consentimiento_publicidad
FROM entrada e
LEFT JOIN salida s ON e.id_usuario = s.id_usuario
`;
// Ejecutar consulta
db.query(query, (err, results) => {
if (err) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Error en la base de datos' }));
return;
}
// Formatear respuesta
const data = results.map((user) => ({
user_id: user.user_id.toString().padStart(3, '0'),
email: user.email,
procedencia: user.procedencia,
rango_edad: user.rango_edad,
proveedor: user.proveedor,
consentimiento_publicidad: user.consentimiento_publicidad,
}));
// Enviar respuesta
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data, null, 2)); // Formato bonito en JSON
});
} else {
// Ruta no encontrada
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Ruta no encontrada');
}
});
// Iniciar servidor en el puerto 3000
server.listen(3001, () => {
console.log('🚀 Servidor Node.js escuchando en http://localhost:3001');
});

91
api_estadisticas.php Normal file
View File

@ -0,0 +1,91 @@
<?php
header('Content-Type: application/json');
require_once 'database/database.php';
$data = [];
// 1. Datos de Género
$queryGenero = "SELECT genero, COUNT(*) AS count FROM entrada GROUP BY genero";
$resultGenero = $conexion->query($queryGenero);
$generoData = [];
$totalGenero = 0;
while ($row = $resultGenero->fetch_assoc()) {
$generoData[$row['genero']] = (int)$row['count'];
$totalGenero += (int)$row['count'];
}
$generoPercentages = [];
foreach ($generoData as $genero => $count) {
$generoPercentages[$genero] = ($totalGenero > 0) ? round(($count / $totalGenero) * 100, 2) : 0;
}
$data['genero'] = $generoPercentages;
// 2. Datos de Edades
$queryEdad = "SELECT edad, COUNT(*) AS count FROM entrada GROUP BY edad ORDER BY
CASE
WHEN edad = 'menor' THEN 1
WHEN edad = '18-21' THEN 2
WHEN edad = '22-25' THEN 3
WHEN edad = '26-30' THEN 4
WHEN edad = '31-35' THEN 5
WHEN edad = '36-40' THEN 6
WHEN edad = '41-45' THEN 7
WHEN edad = '46-50' THEN 8
WHEN edad = '51-55' THEN 9
WHEN edad = '56-60' THEN 10
WHEN edad = 'mayor' THEN 11
ELSE 12
END";
$resultEdad = $conexion->query($queryEdad);
$edadData = [];
$totalEdad = 0;
while ($row = $resultEdad->fetch_assoc()) {
$edadData[$row['edad']] = (int)$row['count'];
$totalEdad += (int)$row['count'];
}
$edadPercentages = [];
foreach ($edadData as $edad => $count) {
$edadPercentages[$edad] = ($totalEdad > 0) ? round(($count / $totalEdad) * 100, 2) : 0;
}
$data['edad'] = $edadPercentages;
// Gráfica de barra de estados que más nos visitan
$queryEstados = "SELECT estado_procedencia, COUNT(*) AS count FROM entrada GROUP BY estado_procedencia ORDER BY count DESC LIMIT 5";
$resultEstados = $conexion->query($queryEstados);
$estadosData = [];
while ($row = $resultEstados->fetch_assoc()) {
$estadosData[$row['estado_procedencia']] = (int)$row['count'];
}
$data['estados'] = $estadosData;
// Gráfica de proveedores de exámenes que más hacen examen o colocan en el formulario
$queryProveedores = "SELECT empresa, COUNT(*) AS count FROM entrada GROUP BY empresa ORDER BY count DESC LIMIT 5";
$resultProveedores = $conexion->query($queryProveedores);
$proveedoresData = [];
while ($row = $resultProveedores->fetch_assoc()) {
$proveedoresData[$row['empresa']] = (int)$row['count'];
}
$data['proveedores'] = $proveedoresData;
// 5. Cuantas personas y datos del mes pasado (filtrado por fecha a fecha)
$fechaActual = date('Y-m-d');
$fechaMesPasadoInicio = date('Y-m-01', strtotime('last month'));
$fechaMesPasadoFin = date('Y-m-t', strtotime('last month'));
$queryMesPasado = "SELECT DATE(fecha) AS dia, COUNT(*) AS count FROM entrada
WHERE fecha BETWEEN '$fechaMesPasadoInicio' AND '$fechaMesPasadoFin'
GROUP BY DATE(fecha) ORDER BY DATE(fecha) ASC";
$resultMesPasado = $conexion->query($queryMesPasado);
$mesPasadoData = [];
while ($row = $resultMesPasado->fetch_assoc()) {
$mesPasadoData[$row['dia']] = (int)$row['count'];
}
$data['mes_pasado'] = $mesPasadoData;
echo json_encode($data);
$conexion->close();
?>

View File

@ -0,0 +1,29 @@
<?php
header('Content-Type: application/json');
require_once 'database/database.php';
$startDate = $_GET['startDate'] ?? null;
$endDate = $_GET['endDate'] ?? null;
$data = [
'fechas_rango' => []
];
if ($startDate && $endDate) {
$query = "SELECT DATE(fecha) AS dia, COUNT(*) AS count FROM entrada
WHERE fecha BETWEEN ? AND ? GROUP BY DATE(fecha) ORDER BY DATE(fecha) ASC";
$stmt = $conexion->prepare($query);
$stmt->bind_param("ss", $startDate, $endDate);
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
$data['fechas_rango'][$row['dia']] = (int)$row['count'];
}
$stmt->close();
}
echo json_encode($data);
$conexion->close();
?>

BIN
assets/face_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

BIN
assets/face_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

BIN
assets/face_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

BIN
assets/face_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

BIN
assets/face_5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

BIN
assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

5
cierre.php Normal file
View File

@ -0,0 +1,5 @@
<?php
session_start();
session_destroy();
header("Location: sesion.php");
exit;

165
dashboard.php Normal file
View File

@ -0,0 +1,165 @@
<?php
session_start();
if (!isset($_SESSION['admin'])) {
header("Location: sesion.php");
exit;
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LANIA Dashboard</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet">
<link href="style/dashboard.css" rel="stylesheet">
<style>
/* Estilo para el botón de filtro de fecha activo */
.date-filter-btn.active {
background-color: #0d6efd;
color: white;
border-color: #0d6efd;
}
/* Estilos para los contenedores de los gráficos para asegurar que se muestren correctamente */
.chart-container {
position: relative;
height: 300px; /* Ajusta esta altura según sea necesario para tus gráficos */
width: 100%;
}
/* Para que las tarjetas tengan una altura consistente si sus contenidos son diferentes */
.card {
height: 100%;
}
.card-body {
display: flex;
align-items: center;
justify-content: center;
height: calc(100% - 56px); /* Altura del card-header es aproximadamente 56px */
}
</style>
</head>
<body>
<?php include 'sidebar.php'; ?>
<div class="main-content">
<div id="estadisticas" class="section">
<div class="row mt-4">
<div class="col-md-4 mb-4">
<div class="card">
<div class="card-header">
<h5 class="card-title">Empresas de Origen</h5>
</div>
<div class="card-body">
<div class="chart-container">
<canvas id="empresasChart"></canvas>
</div>
</div>
</div>
</div>
<div class="col-md-4 mb-4">
<div class="card">
<div class="card-header">
<h5 class="card-title">Estados de Procedencia</h5>
</div>
<div class="card-body">
<div class="chart-container">
<canvas id="estadosChart"></canvas>
</div>
</div>
</div>
</div>
<div class="col-md-4 mb-4">
<div class="card">
<div class="card-header">
<h5 class="card-title">Porcentaje de Usuarios por Género</h5>
</div>
<div class="card-body">
<div class="chart-container">
<canvas id="generoChart"></canvas>
</div>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">
<h5 class="card-title">Porcentaje de Usuarios por Rango de Edad</h5>
</div>
<div class="card-body">
<div class="chart-container">
<canvas id="edadChart"></canvas>
</div>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">
<h5 class="card-title">Usuarios Registrados el Mes Pasado</h5>
</div>
<div class="card-body">
<div class="chart-container">
<canvas id="mesPasadoChart"></canvas>
</div>
</div>
</div>
</div>
</div>
<div class="row mt-4 justify-content-center">
<div class="col-md-8 col-lg-6 mb-4">
<div class="card">
<div class="card-header">
<h5 class="card-title">Usuarios por Rango de Fechas</h5>
</div>
<div class="card-body d-flex flex-column align-items-center"> <div class="mb-3 d-flex flex-column align-items-center">
<button class="btn btn-outline-secondary mb-2 date-filter-btn" data-period="week" onclick="setQuickDateFilter('week', this)">Última Semana</button>
<button class="btn btn-outline-secondary mb-2 date-filter-btn" data-period="last_month_full" onclick="setQuickDateFilter('last_month_full', this)">Último Mes</button>
<button class="btn btn-outline-secondary date-filter-btn" data-period="last_year_full" onclick="setQuickDateFilter('last_year_full', this)">Último Año</button>
</div>
<div class="chart-container mt-4">
<canvas id="dateRangeChart"></canvas>
</div>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12 text-end">
<button class="btn btn-success me-2" onclick="exportData('csv')">
<i class="fas fa-file-csv"></i> Exportar CSV
</button>
<button class="btn btn-danger" onclick="exportData('pdf')">
<i class="fas fa-file-pdf"></i> Exportar PDF
</button>
</div>
</div>
</div>
<div id="entrada" class="section" style="display: none;">
<iframe src="FormularioInicial.php" width="100%" height="600px" frameborder="0"></iframe>
</div>
<div id="salida" class="section" style="display: none;">
<iframe src="FormularioSalida.php" width="100%" height="900px" frameborder="0"></iframe>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="js/dashboard.js"></script>
</body>
</html>

16
database/database.php Normal file
View File

@ -0,0 +1,16 @@
<?php
$host = "localhost";
$user = "root";
$pass = "Starfox19.";
$db = "lania";
$conexion = new mysqli($host, $user, $pass, $db);
// Verifica errores de conexión
if ($conexion->connect_error) {
die("Error de conexión: " . $conexion->connect_error);
}
return $conexion;
?>

239
export.php Normal file
View File

@ -0,0 +1,239 @@
<?php
// export.php
require_once 'database/database.php'; // Ajusta esta ruta si es necesario
require('fpdf186/fpdf.php'); // Asegúrate que esta ruta a FPDF sea correcta
if ($conexion->connect_error) {
die("Error de conexión a la base de datos: " . $conexion->connect_error);
}
$type = $_GET['type'] ?? 'csv'; // csv o pdf
$report = $_GET['report'] ?? 'all_users'; // all_users o by_date_range
$startDate = $_GET['startDate'] ?? null;
$endDate = $_GET['endDate'] ?? null;
// Validar y limpiar fechas para evitar SQL Injection (¡muy importante!)
if ($startDate) $startDate = $conexion->real_escape_string($startDate);
if ($endDate) $endDate = $conexion->real_escape_string($endDate);
$query = "SELECT id_usuario, nombre, correo, telefono, genero, edad, estado_procedencia, identificacion, empresa, examen, certificacion, h_entrada, fecha FROM entrada";
$params = [];
$paramTypes = "";
// Construir la consulta SQL basada en el tipo de reporte
if ($report === 'by_date_range' && $startDate && $endDate) {
// Si queremos "todo el mes anterior" o "todo el año anterior", las fechas ya vendrán
// calculadas desde el frontend.
$query .= " WHERE fecha BETWEEN ? AND ?";
$params = [$startDate, $endDate];
$paramTypes = "ss"; // 's' para string, dos strings
} else {
// Si 'all_users' o no hay fechas válidas, exporta todo.
// Esto ya no debería ser el caso si el JS siempre envía fechas.
// Podrías poner un error aquí si esperas siempre un rango.
// Forzar un rango predeterminado si no llega nada?
// current month by default
$startDate = date('Y-m-01');
$endDate = date('Y-m-t');
$query .= " WHERE fecha BETWEEN ? AND ?";
$params = [$startDate, $endDate];
$paramTypes = "ss";
}
$stmt = $conexion->prepare($query);
if ($stmt === false) {
die("Error al preparar la consulta: " . $conexion->error);
}
if (!empty($params)) {
$stmt->bind_param($paramTypes, ...$params);
}
$stmt->execute();
$result = $stmt->get_result();
if ($result === false) {
die("Error al ejecutar la consulta: " . $conexion->error);
}
$data = [];
while ($row = $result->fetch_assoc()) {
$data[] = $row;
}
$stmt->close();
$conexion->close();
if (empty($data)) {
// No hay datos para exportar
if ($type === 'csv') {
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="reporte_vacio.csv"');
echo "No hay datos para el rango de fechas seleccionado.";
} elseif ($type === 'pdf') {
class PDF_Empty extends FPDF {
function Header() { $this->SetFont('Arial','B',12); $this->Cell(0,10,utf8_decode('Reporte de Usuarios Registrados'),0,1,'C'); }
function Footer() { $this->SetY(-15); $this->SetFont('Arial','I',8); $this->Cell(0,10,utf8_decode('Página ').$this->PageNo().'/{nb}',0,0,'C'); }
function Content() {
$this->SetFont('Arial','',10);
$this->Cell(0, 10, utf8_decode('No hay datos para el rango de fechas seleccionado.'), 0, 1, 'C');
}
}
$pdfEmpty = new PDF_Empty();
$pdfEmpty->AddPage();
$pdfEmpty->Content();
$pdfEmpty->Output('I', 'reporte_vacio.pdf');
}
exit();
}
if ($type === 'csv') {
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="reporte_usuarios_' . date('Ymd_His') . '.csv"');
$output = fopen('php://output', 'w');
// Nombres de las columnas (encabezado del CSV)
fputcsv($output, array_keys($data[0]));
// Datos
foreach ($data as $row) {
fputcsv($output, $row);
}
fclose($output);
} elseif ($type === 'pdf') {
class PDF extends FPDF
{
protected $startDate;
protected $endDate;
function setDateRange($start, $end) {
$this->startDate = $start;
$this->endDate = $end;
}
function Header()
{
$this->SetFont('Arial', 'B', 12);
$this->Cell(0, 10, utf8_decode('Reporte de Usuarios Registrados'), 0, 1, 'C');
$this->SetFont('Arial', '', 10);
if ($this->startDate && $this->endDate) {
$this->Cell(0, 10, utf8_decode('Rango de Fechas: ' . $this->startDate . ' a ' . $this->endDate), 0, 1, 'C');
}
$this->Ln(5);
}
function Footer()
{
$this->SetY(-15);
$this->SetFont('Arial', 'I', 8);
$this->Cell(0, 10, utf8_decode('Página ') . $this->PageNo() . '/{nb}', 0, 0, 'C');
}
function ImprovedTable($header, $data)
{
// Anchuras de las columnas (reajusta según tus datos y el tamaño de la página A4 apaisada ~277mm)
// Haz pruebas para que quepan todos tus datos. Si tienes demasiadas columnas, considera una fuente más pequeña.
$w = [10, 25, 40, 20, 15, 15, 25, 20, 20, 20, 20, 20, 20]; // Suma: 270mm (ajusta hasta 277mm)
// id, nombre, correo, telefono, genero, edad, estado, identificacion, empresa, examen, certificacion, h_entrada, fecha
$this->SetFillColor(200, 220, 255);
$this->SetTextColor(0);
$this->SetDrawColor(0, 0, 0);
$this->SetLineWidth(.3);
$this->SetFont('Arial', 'B', 7); // Fuente más pequeña para el encabezado
// Cabecera
for ($i = 0; $i < count($header); $i++) {
$this->Cell($w[$i], 7, utf8_decode($header[$i]), 1, 0, 'C', true);
}
$this->Ln();
$this->SetFillColor(224, 235, 255);
$this->SetTextColor(0);
$this->SetFont('Arial', '', 7); // Fuente más pequeña para los datos
$fill = false;
foreach ($data as $row) {
// Para calcular la altura de la fila basándose en el contenido de las celdas
$maxHeight = 6;
$cellContents = [];
foreach ($row as $key => $value) {
$cellContents[$key] = utf8_decode($value);
}
// Ajusta los índices de las columnas y sus anchos correspondientes
$columnsToCheck = [
'nombre' => $w[1], 'correo' => $w[2], 'estado_procedencia' => $w[6],
'empresa' => $w[8], 'examen' => $w[9], 'certificacion' => $w[10]
];
foreach($columnsToCheck as $colName => $colWidth) {
// Calcula la altura mínima para el texto si se ajusta al ancho de la celda
$neededHeight = $this->GetStringWidth($cellContents[$colName]) / $colWidth * $this->FontSize / 2.5; // Estimación
if ($neededHeight > $maxHeight) {
$maxHeight = $neededHeight;
}
}
$maxHeight = max($maxHeight, $this->FontSize + 2); // Asegura una altura mínima
// Verificar si se necesita una nueva página
if ($this->GetY() + $maxHeight > $this->PageBreakTrigger) {
$this->AddPage($this->CurOrientation);
// Volver a imprimir la cabecera de la tabla en la nueva página
$this->SetFillColor(200, 220, 255);
$this->SetTextColor(0);
$this->SetDrawColor(0, 0, 0);
$this->SetLineWidth(.3);
$this->SetFont('Arial', 'B', 7);
for ($i = 0; $i < count($header); $i++) {
$this->Cell($w[$i], 7, utf8_decode($header[$i]), 1, 0, 'C', true);
}
$this->Ln();
$this->SetFillColor(224, 235, 255);
$this->SetTextColor(0);
$this->SetFont('Arial', '', 7);
}
// Imprimir las celdas de la fila
$this->Cell($w[0], $maxHeight, $row['id_usuario'], 'LR', 0, 'C', $fill);
$this->Cell($w[1], $maxHeight, utf8_decode(mb_strimwidth($row['nombre'], 0, floor($w[1]/1.5), "...")), 'LR', 0, 'L', $fill);
$this->Cell($w[2], $maxHeight, utf8_decode(mb_strimwidth($row['correo'], 0, floor($w[2]/1.5), "...")), 'LR', 0, 'L', $fill);
$this->Cell($w[3], $maxHeight, utf8_decode($row['telefono']), 'LR', 0, 'L', $fill);
$this->Cell($w[4], $maxHeight, utf8_decode($row['genero']), 'LR', 0, 'C', $fill);
$this->Cell($w[5], $maxHeight, utf8_decode($row['edad']), 'LR', 0, 'C', $fill);
$this->Cell($w[6], $maxHeight, utf8_decode(mb_strimwidth($row['estado_procedencia'], 0, floor($w[6]/1.5), "...")), 'LR', 0, 'L', $fill);
$this->Cell($w[7], $maxHeight, utf8_decode($row['identificacion']), 'LR', 0, 'L', $fill);
$this->Cell($w[8], $maxHeight, utf8_decode(mb_strimwidth($row['empresa'], 0, floor($w[8]/1.5), "...")), 'LR', 0, 'L', $fill);
$this->Cell($w[9], $maxHeight, utf8_decode(mb_strimwidth($row['examen'], 0, floor($w[9]/1.5), "...")), 'LR', 0, 'L', $fill);
$this->Cell($w[10], $maxHeight, utf8_decode(mb_strimwidth($row['certificacion'], 0, floor($w[10]/1.5), "...")), 'LR', 0, 'L', $fill);
$this->Cell($w[11], $maxHeight, utf8_decode($row['h_entrada']), 'LR', 0, 'C', $fill);
$this->Cell($w[12], $maxHeight, utf8_decode($row['fecha']), 'LR', 0, 'C', $fill);
$this->Ln($maxHeight);
$fill = !$fill;
}
$this->Cell(array_sum($w), 0, '', 'T');
}
}
$pdf = new PDF();
$pdf->AliasNbPages();
$pdf->AddPage('L'); // 'L' para orientación horizontal
$pdf->setDateRange($startDate, $endDate); // Pasar las fechas a la clase PDF
// Encabezados de la tabla (ajusta estos a tus columnas de la BD)
$header = array('ID', 'Nombre', 'Correo', 'Teléfono', 'Género', 'Edad', 'Estado', 'Identificación', 'Empresa', 'Examen', 'Certificación', 'H. Entrada', 'Fecha');
$pdf->ImprovedTable($header, $data);
$pdf->Output('I', 'reporte_usuarios_' . date('Ymd_His') . '.pdf');
} else {
echo "Tipo de exportación no soportado.";
}
?>

70
inicio.php Normal file
View File

@ -0,0 +1,70 @@
<?php
// inicio.php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Logout
if (isset($_GET['action']) && $_GET['action'] === 'logout') {
session_destroy();
header('Location: inicio.php');
exit;
}
// Selección de rol
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['role'])) {
session_unset();
$_SESSION['role'] = $_POST['role'];
if ($_POST['role'] === 'admin') {
header('Location: sesion.php');
} else {
header('Location: FormularioInicial.php');
}
exit;
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Inicio - LANIA</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
margin: 0;
height: 100vh;
background: #001f3f; /* azul marino */
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.logo {
max-width: 300px;
width: 100%;
margin-bottom: 2rem;
}
.btn-role {
width: 220px;
margin-bottom: 1rem;
}
form {
display: flex;
flex-direction: column;
align-items: center;
}
</style>
</head>
<body>
<img src="assets/logo.png" alt="Logo LANIA" class="logo">
<form method="post">
<button type="submit" name="role" value="aspirant" class="btn btn-outline-light btn-role">
Iniciar como Aspirante
</button>
<button type="submit" name="role" value="admin" class="btn btn-light btn-role">
Iniciar como Administrador
</button>
</form>
</body>
</html>

307
js/dashboard.js Normal file
View File

@ -0,0 +1,307 @@
let generoChartInstance, edadChartInstance, estadosChartInstance, empresasChartInstance, mesPasadoChartInstance, dateRangeChartInstance;
let currentStartDate = '';
let currentEndDate = '';
function showSection(sectionId) {
document.querySelectorAll('.section').forEach(section => {
section.style.display = 'none';
});
document.getElementById(sectionId).style.display = 'block';
document.querySelectorAll('.sidebar-menu li').forEach(item => {
item.classList.remove('active');
});
if (sectionId === 'estadisticas') {
document.getElementById('menu-estadisticas').classList.add('active');
}
}
function toggleSidebar() {
document.querySelector('.sidebar').classList.toggle('active');
document.querySelector('.main-content').classList.toggle('active');
}
function initCharts() {
if (generoChartInstance) generoChartInstance.destroy();
if (edadChartInstance) edadChartInstance.destroy();
if (estadosChartInstance) estadosChartInstance.destroy();
if (empresasChartInstance) empresasChartInstance.destroy();
if (mesPasadoChartInstance) mesPasadoChartInstance.destroy();
if (dateRangeChartInstance) dateRangeChartInstance.destroy();
fetch('api_estadisticas.php')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log("Datos recibidos de la API:", data);
// Gráfico de Empresas de Origen
const empresasCtx = document.getElementById('empresasChart');
if (empresasCtx) {
empresasChartInstance = new Chart(empresasCtx.getContext('2d'), {
type: 'doughnut',
data: {
labels: Object.keys(data.proveedores),
datasets: [{
data: Object.values(data.proveedores),
backgroundColor: ['#1A73E8', '#4CAF50', '#FF9800', '#9C27B0']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { position: 'bottom' } }
}
});
}
// Gráfico de Estados de Procedencia
const estadosCtx = document.getElementById('estadosChart');
if (estadosCtx) {
estadosChartInstance = new Chart(estadosCtx.getContext('2d'), {
type: 'bar',
data: {
labels: Object.keys(data.estados),
datasets: [{
label: 'Usuarios',
data: Object.values(data.estados),
backgroundColor: 'rgba(26, 115, 232, 0.7)'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: { y: { beginAtZero: true } }
}
});
}
// Gráfico de Género
const generoCtx = document.getElementById('generoChart');
if (generoCtx) {
generoChartInstance = new Chart(generoCtx.getContext('2d'), {
type: 'doughnut',
data: {
labels: Object.keys(data.genero),
datasets: [{
data: Object.values(data.genero),
backgroundColor: ['#E91E63', '#1A73E8', '#9C27B0']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'bottom' },
tooltip: { callbacks: { label: function(context) { let label = context.label || ''; if (label) { label += ': '; } if (context.parsed !== null) { label += context.parsed + '%'; } return label; } } }
}
}
});
}
// Gráfico de Edad
const edadCtx = document.getElementById('edadChart');
if (edadCtx) {
edadChartInstance = new Chart(edadCtx.getContext('2d'), {
type: 'pie',
data: {
labels: Object.keys(data.edad),
datasets: [{
data: Object.values(data.edad),
backgroundColor: ['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'bottom' },
tooltip: { callbacks: { label: function(context) { let label = context.label || ''; if (label) { label += ': '; } if (context.parsed !== null) { label += context.parsed + '%'; } return label; } } }
}
}
});
}
// Gráfico de Usuarios del Mes Pasado
const mesPasadoCtx = document.getElementById('mesPasadoChart');
if (mesPasadoCtx) {
mesPasadoChartInstance = new Chart(mesPasadoCtx.getContext('2d'), {
type: 'line',
data: {
labels: Object.keys(data.mes_pasado),
datasets: [{
label: 'Usuarios Registrados',
data: Object.values(data.mes_pasado),
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1,
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: true } },
scales: {
x: { title: { display: true, text: 'Día del Mes Pasado' } },
y: { beginAtZero: true, title: { display: true, text: 'Cantidad de Usuarios' } }
}
}
});
}
// Inicializar Gráfico para Rango de Fechas (vacío al inicio, se llena con updateChartsByDate)
const dateRangeCtx = document.getElementById('dateRangeChart');
if (dateRangeCtx) {
if (!dateRangeChartInstance) {
dateRangeChartInstance = new Chart(dateRangeCtx.getContext('2d'), {
type: 'bar',
data: {
labels: [],
datasets: [{
label: 'Usuarios Registrados',
data: [],
backgroundColor: 'rgba(54, 162, 235, 0.7)'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: { y: { beginAtZero: true } }
}
});
}
}
})
.catch(error => {
console.error('Error al obtener datos de estadísticas:', error);
alert('No se pudieron cargar las estadísticas. Por favor, verifica la conexión y los datos.');
});
}
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
function setQuickDateFilter(period, clickedButton = null) {
const today = new Date();
let startDate = new Date();
let endDate = new Date(); // Por defecto, hoy
if (period === 'week') {
startDate.setDate(today.getDate() - 7);
} else if (period === 'last_month_full') {
startDate = new Date(today.getFullYear(), today.getMonth() - 1, 1);
endDate = new Date(today.getFullYear(), today.getMonth(), 0);
} else if (period === 'last_year_full') {
startDate = new Date(today.getFullYear() - 1, 0, 1);
endDate = new Date(today.getFullYear() - 1, 11, 31);
}
currentStartDate = formatDate(startDate);
currentEndDate = formatDate(endDate);
document.querySelectorAll('.date-filter-btn').forEach(btn => {
btn.classList.remove('active');
});
if (clickedButton) {
clickedButton.classList.add('active');
}
updateChartsByDate();
}
function updateChartsByDate() {
const startDate = currentStartDate;
const endDate = currentEndDate;
if (!startDate || !endDate) {
console.warn('No hay rango de fechas seleccionado para el gráfico de rango. Inicializando con "Último Mes".');
setQuickDateFilter('last_month_full', document.querySelector('.date-filter-btn[data-period="last_month_full"]'));
return;
}
fetch(`api_estadisticas_fecha.php?startDate=${startDate}&endDate=${endDate}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log("Datos por rango de fechas:", data);
const dateRangeCtx = document.getElementById('dateRangeChart');
if (dateRangeCtx) {
if (dateRangeChartInstance) {
dateRangeChartInstance.destroy();
}
dateRangeChartInstance = new Chart(dateRangeCtx.getContext('2d'), {
type: 'bar',
data: {
labels: Object.keys(data.fechas_rango),
datasets: [{
label: 'Usuarios Registrados',
data: Object.values(data.fechas_rango),
backgroundColor: 'rgba(54, 162, 235, 0.7)'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: {
y: {
beginAtZero: true,
ticks: {
padding: 20
}
}
}
}
});
}
})
.catch(error => {
console.error('Error al obtener datos por rango de fechas:', error);
alert('No se pudieron cargar los datos para el rango de fechas seleccionado. Verifica la conexión y los datos en la base de datos.');
});
}
function exportData(format) {
const startDate = currentStartDate;
const endDate = currentEndDate;
if (!startDate || !endDate) {
alert('Por favor, selecciona un rango de fechas (Última Semana, Mes o Año) antes de exportar.');
return;
}
let url = `export.php?type=${format}&report=by_date_range&startDate=${startDate}&endDate=${endDate}`;
console.log("URL de exportación:", url);
window.open(url, '_blank');
}
document.addEventListener('DOMContentLoaded', function() {
showSection('estadisticas');
initCharts();
setQuickDateFilter('last_month_full', document.querySelector('.date-filter-btn[data-period="last_month_full"]'));
});

View File

@ -0,0 +1,19 @@
<?php
require_once '../database/database.php';
$datos = [];
$sql = "SELECT empresa, estado_procedencia, edad FROM entrada";
if ($result = $conexion->query($sql)) {
while ($row = $result->fetch_assoc()) {
$datos[] = $row;
}
} else {
http_response_code(500);
echo json_encode(["error" => "Error en la consulta SQL"]);
exit;
}
header('Content-Type: application/json');
echo json_encode($datos);
?>

29
model/inicio.php Normal file
View File

@ -0,0 +1,29 @@
<?php
session_start();
$conn = require_once __DIR__ . '/../database/database.php';
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$nombre = $_POST['nombre'];
$password = $_POST['password'];
$stmt = $conn->prepare("SELECT * FROM admin WHERE nombre = ?");
$stmt->bind_param("s", $nombre);
$stmt->execute();
$result = $stmt->get_result();
if ($admin = $result->fetch_assoc()) {
if (password_verify($password, $admin['password'])) {
$_SESSION['admin'] = $admin['nombre'];
header("Location: dashboard.php");
exit;
} else {
$error = "⚠️ Contraseña incorrecta.";
}
} else {
$error = "⚠️ Usuario no encontrado.";
}
$stmt->close();
}
?>

35
model/login.php Normal file
View File

@ -0,0 +1,35 @@
<?php
require '../database/database.php';
date_default_timezone_set('America/Mexico_City');
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$nombre = $_POST['nombre'] ?? '';
$correo = $_POST['correo'] ?? '';
$telefono = $_POST['telefono'] ?? '';
$genero = $_POST['genero'] ?? '';
$edad = $_POST['edad'] ?? '';
$estado_procedencia = $_POST['estado_procedencia'] ?? '';
$identificacion = $_POST['identificacion'] ?? '';
$empresa = $_POST['empresa'] ?? '';
$examen = $_POST['examen'] ?? '';
$certificacion = $_POST['certificacion'] ?? '';
$hora_entrada = $_POST['h_entrada'] ?: date("H:i:s"); // si no se proporciona, toma hora actual
$fecha = $_POST['fecha'] ?: date("Y-m-d"); // si no se proporciona, toma fecha actual
$sql = "INSERT INTO entrada (nombre, correo, telefono, genero, edad, estado_procedencia, identificacion, empresa, examen, certificacion, h_entrada, fecha)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = $conexion->prepare($sql);
$stmt->bind_param("ssssssssssss", $nombre, $correo, $telefono, $genero, $edad, $estado_procedencia, $identificacion, $empresa, $examen, $certificacion, $hora_entrada, $fecha);
if ($stmt->execute()) {
echo "Entrada guardada correctamente.";
} else {
echo "Error al guardar: " . $stmt->error;
}
$stmt->close();
$conexion->close();
}
?>

55
model/logout.php Normal file
View File

@ -0,0 +1,55 @@
<?php
require_once '../database/database.php';
// Sanitizar y obtener valores
$id_usuario = $_POST['id_usuario'];
$hora_salida = $_POST['hora_salida'];
$consentimiento = isset($_POST['consentimiento']) ? $_POST['consentimiento'] : 'no';
// 1. Insertar la salida
$sqlSalida = "INSERT INTO salida (id_usuario, hora_salida, consentimiento) VALUES (?, ?, ?)";
$stmtSalida = $conexion->prepare($sqlSalida);
$stmtSalida->bind_param("iss", $id_usuario, $hora_salida, $consentimiento);
$stmtSalida->execute();
$id_salida = $stmtSalida->insert_id; // Obtener el ID generado
// 2. Insertar la encuesta (si se llenó)
$campos_encuesta = [
'atencion_personal',
'equipo_funcionando',
'ambiente_aula',
'calidad_internet',
'instrucciones_claras',
'respuesta_personal',
'recomendacion_lania'
];
$valores_encuesta = [];
foreach ($campos_encuesta as $campo) {
$valores_encuesta[$campo] = isset($_POST[$campo]) ? intval($_POST[$campo]) : null;
}
// Solo guarda si hay al menos una respuesta
if (array_filter($valores_encuesta)) {
$sqlEncuesta = "INSERT INTO encuesta_satisfaccion (
id_salida, atencion_personal, equipo_funcionando, ambiente_aula, calidad_internet,
instrucciones_claras, respuesta_personal, recomendacion_lania
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
$stmtEncuesta = $conexion->prepare($sqlEncuesta);
$stmtEncuesta->bind_param(
"iiiiiiii",
$id_salida,
$valores_encuesta['atencion_personal'],
$valores_encuesta['equipo_funcionando'],
$valores_encuesta['ambiente_aula'],
$valores_encuesta['calidad_internet'],
$valores_encuesta['instrucciones_claras'],
$valores_encuesta['respuesta_personal'],
$valores_encuesta['recomendacion_lania']
);
$stmtEncuesta->execute();
}
echo "Salida y encuesta registradas correctamente.";

80
model/register.php Normal file
View File

@ -0,0 +1,80 @@
<?php
$conn = require_once __DIR__ . '/../database/database.php';
// Si la conexión falló, $conn será false
if (!($conn instanceof mysqli)) {
// error_log('Fallo conexión a BD en register.php');
}
/*
* Registro directo al recibir POST en este archivo
*/
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_POST['nombre'], $_POST['password'])) {
if (!($conn instanceof mysqli)) {
$error = "❌ Error interno de conexión.";
} else {
$nombre = $_POST['nombre'];
$password = password_hash($_POST['password'], PASSWORD_DEFAULT);
$stmt = $conn->prepare("INSERT INTO admin (nombre, password) VALUES (?, ?)");
$stmt->bind_param("ss", $nombre, $password);
if ($stmt->execute()) {
$success = "✅ Administrador registrado con éxito.";
} else {
$error = "❌ Error al registrar: " . $stmt->error;
}
$stmt->close();
}
}
/**
* Función reutilizable: crea un admin y devuelve true o mensaje de error.
*/
function createAdmin(string $nombre, string $password) {
global $conn;
if (!($conn instanceof mysqli)) {
return "Error de conexión a la base de datos.";
}
$hash = password_hash($password, PASSWORD_DEFAULT);
$stmt = $conn->prepare("INSERT INTO admin (nombre, password) VALUES (?, ?)");
if (!$stmt) {
return "Error en prepare(): " . $conn->error;
}
$stmt->bind_param("ss", $nombre, $hash);
if ($stmt->execute()) {
$stmt->close();
return true;
} else {
$err = $stmt->error;
$stmt->close();
return $err;
}
}
/**
* Función para verificar credenciales. Devuelve true solo si el usuario existe y la contraseña coincide. Si la conexión falla, devuelve false.
*/
function checkAdmin(string $nombre, string $password): bool {
global $conn;
if (!($conn instanceof mysqli)) {
return false;
}
$stmt = $conn->prepare("SELECT password FROM admin WHERE nombre = ?");
if (!$stmt) {
return false;
}
$stmt->bind_param("s", $nombre);
$stmt->execute();
$stmt->bind_result($hash);
if ($stmt->fetch()) {
$stmt->close();
return password_verify($password, $hash);
}
$stmt->close();
return false;
}

139
package-lock.json generated Normal file
View File

@ -0,0 +1,139 @@
{
"name": "proyecto_LANIA",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"mysql2": "^3.14.1"
}
},
"node_modules/aws-ssl-profiles": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
"license": "MIT",
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10"
}
},
"node_modules/generate-function": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
"license": "MIT",
"dependencies": {
"is-property": "^1.0.2"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-property": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
"license": "MIT"
},
"node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0"
},
"node_modules/lru-cache": {
"version": "7.18.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/lru.min": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz",
"integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==",
"license": "MIT",
"engines": {
"bun": ">=1.0.0",
"deno": ">=1.30.0",
"node": ">=8.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wellwelwel"
}
},
"node_modules/mysql2": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.1.tgz",
"integrity": "sha512-7ytuPQJjQB8TNAYX/H2yhL+iQOnIBjAMam361R7UAL0lOVXWjtdrmoL9HYKqKoLp/8UUTRcvo1QPvK9KL7wA8w==",
"license": "MIT",
"dependencies": {
"aws-ssl-profiles": "^1.1.1",
"denque": "^2.1.0",
"generate-function": "^2.3.1",
"iconv-lite": "^0.6.3",
"long": "^5.2.1",
"lru.min": "^1.0.0",
"named-placeholders": "^1.1.3",
"seq-queue": "^0.0.5",
"sqlstring": "^2.3.2"
},
"engines": {
"node": ">= 8.0"
}
},
"node_modules/named-placeholders": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
"integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==",
"license": "MIT",
"dependencies": {
"lru-cache": "^7.14.1"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/seq-queue": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
"integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
},
"node_modules/sqlstring": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
"integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
}
}
}

5
package.json Normal file
View File

@ -0,0 +1,5 @@
{
"dependencies": {
"mysql2": "^3.14.1"
}
}

72
registro.php Normal file
View File

@ -0,0 +1,72 @@
s<?php
require_once 'model/register.php';
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Si ya está logueado como admin
if (isset($_SESSION['admin_logged']) && $_SESSION['admin_logged'] === true) {
header('Location: dashboard.php');
exit;
}
// Procesar registro
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$nombre = $_POST['nombre'];
$password = $_POST['password'];
$res = createAdmin($nombre, $password);
if ($res === true) {
$success = 'Administrador registrado correctamente.';
} else {
$error = 'Error al registrar: ' . $res;
}
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Registro Admin</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet">
<style>
body {
margin: 0;
height: 100vh;
background: #001f3f;
display: flex;
align-items: center;
justify-content: center;
}
.card {
width: 100%;
max-width: 400px;
padding: 2rem;
border-radius: .75rem;
background: white;
}
</style>
</head>
<body>
<div class="card shadow">
<h1 class="h4 text-center mb-4">Registrar Administrador</h1>
<form method="post">
<label for="nombre" class="form-label">Nombre:</label>
<input type="text" id="nombre" name="nombre" class="form-control" required><br>
<label for="password" class="form-label">Contraseña:</label>
<input type="password" id="password" name="password" class="form-control" required><br>
<div class="d-flex justify-content-between mt-4">
<button type="submit" class="btn btn-primary">Registrar</button>
<a href="sesion.php" class="btn btn-secondary">Ir al Login</a>
</div>
</form>
<?php if (isset($success)): ?>
<div class="alert alert-success mt-3"><?= $success ?></div>
<?php elseif (isset($error)): ?>
<div class="alert alert-danger mt-3"><?= $error ?></div>
<?php endif; ?>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

76
sesion.php Normal file
View File

@ -0,0 +1,76 @@
<?php
require_once 'model/inicio.php';
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Si ya está logueado como admin
if (isset($_SESSION['admin_logged']) && $_SESSION['admin_logged'] === true) {
header('Location: dashboard.php');
exit;
}
// Procesar login
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$nombre = $_POST['nombre'];
$password = $_POST['password'];
require_once 'model/register.php';
if (checkAdmin($nombre, $password)) {
$_SESSION['admin_logged'] = true;
$_SESSION['role'] = 'admin';
header('Location: dashboard.php');
exit;
} else {
// En vez de fatal, simple mensaje y form sigue disponible
$error = 'Nombre o contraseña incorrectos.';
}
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Inicio de Sesión - Admin</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet">
<style>
body {
margin: 0;
height: 100vh;
background: #001f3f;
display: flex;
align-items: center;
justify-content: center;
}
.card {
width: 100%;
max-width: 400px;
padding: 2rem;
border-radius: .75rem;
background: white;
}
</style>
</head>
<body>
<div class="card shadow">
<h1 class="h4 text-center mb-4">Sesión Administrador</h1>
<form method="post">
<label for="nombre" class="form-label">Nombre:</label>
<input type="text" id="nombre" name="nombre" class="form-control" required><br>
<label for="password" class="form-label">Contraseña:</label>
<input type="password" id="password" name="password" class="form-control" required><br>
<div class="d-flex justify-content-between mt-4">
<button type="submit" class="btn btn-primary">Iniciar sesión</button>
<a href="registro.php" class="btn btn-secondary">Regist. n. admin</a>
</div>
</form>
<?php if (isset($error)): ?>
<div class="alert alert-danger mt-3"><?= $error ?></div>
<?php endif; ?>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

58
sidebar.php Normal file
View File

@ -0,0 +1,58 @@
<?php
// 1) Iniciar sesión si no está activa
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// 2) Logout: si cierra sesión destruimos y redirigimos
if (isset($_GET['action']) && $_GET['action'] === 'logout') {
session_destroy();
header('Location: inicio.php');
exit;
}
// 3) Detectar rol actual en $_SESSION['role']
$role = $_SESSION['role'] ?? null;
?>
<div class="sidebar">
<div class="sidebar-header text-center">
<h4>LANIA</h4>
</div>
<ul class="sidebar-menu">
<!-- Solo para administradores -->
<?php if ($role === 'admin'): ?>
<li id="menu-estadisticas"> <a href="dashboard.php">
<span class="menu-icon-wrapper"><i class="fas fa-tachometer-alt"></i></span>
<span class="menu-text">Estadísticas</span>
</a>
</li>
<?php endif; ?>
<!-- Siempre visibles -->
<li id="menu-entrada"> <a href="FormularioInicial.php">
<span class="menu-icon-wrapper"><i class="fas fa-user"></i></span>
<span class="menu-text">Formulario de Ingreso</span>
</a>
</li>
<li id="menu-salida"> <a href="FormularioSalida.php">
<span class="menu-icon-wrapper"><i class="fas fa-user"></i></span>
<span class="menu-text">Formulario de Salida</span>
</a>
</li>
</ul>
<!-- Cerrar / Iniciar sesión -->
<div class="logout-btn text-center">
<?php if ($role === 'admin' || $role === 'aspirant'): ?>
<a href="?action=logout" class="btn btn-danger">
<i class="fas fa-sign-out-alt"></i> Cerrar Sesión
</a>
<?php else: ?>
<a href="inicio.php" class="btn btn-primary">
<i class="fas fa-sign-in-alt"></i> Iniciar Sesión
</a>
<?php endif; ?>
</div>
</div>

213
style/dashboard.css Normal file
View File

@ -0,0 +1,213 @@
:root {
--primary:rgb(51, 76, 109);
--secondary: #6c757d;
--success: #28a745;
--info: #17a2b8;
--warning: #ffc107;
--danger: #dc3545;
--light:hsl(210, 16.70%, 97.60%);
--dark: #343a40;
--sidebar-bg: #1A2035;
--card-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.14), 0 7px 10px -5px rgba(0, 0, 0, 0.4);
}
body {
background-color: #f0f2f5;
font-family: 'Roboto', sans-serif;
}
.sidebar {
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 250px;
background:  #37455a;
color: white;
transition: all 0.3s;
z-index: 1000;
}
.sidebar-header {
color: black;
padding: 20px;
background:#37455a;
text-align: center;
}
.sidebar-menu {
padding: 0;
list-style: none;
margin-top: 20px;
}
.sidebar-menu li a {
display: flex;
align-items: center;
padding: 10px 15px;
border-radius: 8px;
margin: 5px 15px;
background-color: transparent;
color:#4a515a;
text-decoration: none;
transition: background-color 0.3s ease, color 0.3s ease;
}
.sidebar-menu li a:hover {
background-color: rgba(255, 255, 255, 0.1);
color: white;
}
.menu-icon-wrapper {
display: flex;
justify-content: center;
align-items: center;
width: 38px;
height: 38px;
border-radius: 10px;
margin-right: 15px;
background-color: transparent;
transition: background-color 0.3s ease, color 0.3s ease;
}
.menu-icon-wrapper i {
color:rgb(108, 108, 108); ;
font-size: 18px;
}
.sidebar-menu li.active a {
background-color: white;
box-shadow: 0 4px 10px hsla(0, 0.00%, 0.00%, 0.10);
color: var(--primary);
}
.sidebar-menu li.active .menu-icon-wrapper {
background-color: var(--primary);
}
.sidebar-menu li.active .menu-icon-wrapper i {
color: white;
}
.sidebar-menu li.active .menu-text {
color: var(--primary);
font-weight: bold;
}
.main-content {
margin-left: 250px;
padding: 20px;
transition: all 0.3s;
background-color: #f0f2f5;
}
.card {
border: none;
border-radius: 10px;
box-shadow: var(--card-shadow);
margin-bottom: 20px;
transition: transform 0.3s;
}
.card:hover {
transform: translateY(-5px);
}
.card-header {
background: transparent;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.card-body {
display: flex;
flex-direction: column;
align-items: center;
padding: 1.5rem;
}
.card-body .btn {
max-width: 100%;
white-space: normal;
word-wrap: break-word;
}
.stat-card {
text-align: center;
padding: 20px;
}
.stat-card i {
font-size: 30px;
margin-bottom: 10px;
}
.stat-card .count {
font-size: 24px;
font-weight: bold;
}
.stat-card .label {
font-size: 14px;
color: var(--secondary);
}
.stat-card .change {
font-size: 12px;
margin-top: 5px;
}
.change.positive {
color: var(--success);
}
.change.negative {
color: var(--danger);
}
.info-card {
position: relative;
overflow: hidden;
color: white;
background: linear-gradient(135deg, var(--primary) 0%, #5e72e4 100%);
}
.info-card .icon {
position: absolute;
top: 20px;
right: 20px;
font-size: 60px;
opacity: 0.2;
}
.chart-container {
position: relative;
height: 300px;
height: 300px;
}
@media (max-width: 768px) {
.sidebar {
margin-left: -250px;
}
.main-content {
margin-left: 0;
}
.sidebar.active {
margin-left: 0;
}
.main-content.active {
margin-left: 250px;
}
.card-title {
font-size: 1rem;
}
.card-body .btn {
font-size: 0.8rem;
padding: 0.5rem 0.8rem;
}
.chart-container {
height: 250px;
}
}