Compare commits

..

No commits in common. "main" and "backend" have entirely different histories.

41 changed files with 4367 additions and 46 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.idea/

View File

@ -1,46 +0,0 @@
# Sistema de Registro y Gestión de Candidatos - LANIA
Este proyecto es una plataforma web desarrollada para automatizar el registro de usuarios en el **Centro de Certificación LANIA**, con el objetivo de optimizar la recolección de datos demográficos y de satisfacción de los candidatos, así como proporcionar herramientas de análisis al área de administración.
## ✨ Descripción General
La aplicación se compone de dos partes principales:
- **Web App**: Interfaz gráfica donde candidatos pueden llenar formularios y administradores pueden gestionar usuarios, candidatos y consultar datos analíticos.
- **API REST**: Servicio backend que expone endpoints para la gestión de candidatos, autenticación vía OAuth2 y exportación de datos en múltiples formatos (JSON, CSV, Excel).
## 🎯 Propósito
- Automatizar el proceso de registro y seguimiento de candidatos.
- Recolectar datos clave para la toma de decisiones institucionales.
- Facilitar la gestión de usuarios y candidatos por parte del personal de LANIA.
- Brindar herramientas visuales de análisis mediante un dashboard dinámico.
## 👥 Tipos de Usuario
- **Candidato**: Registra sus datos mediante formularios web.
- **Administrador LANIA**: Visualiza, filtra y exporta datos, administra usuarios y accede al dashboard analítico.
## 🛠 Tecnologías Utilizadas
| Tecnología | Propósito |
|-------------------|------------------------------------------------|
| PHP (MVC) | Lógica del servidor y API REST |
| JavaScript | Interacción dinámica en el cliente |
| MySQL | Almacenamiento de datos |
| Chart.js | Visualización de datos en gráficos |
| OAuth2 | Autenticación y autorización segura |
| HTML/CSS | Maquetación y estilos de la interfaz |
| INEGI BD externa | Validación geográfica de estado/municipio |
## 📂 Funcionalidades Principales
- Dashboard con métricas visuales.
- Formularios dinámicos de registro.
- Control y gestión de candidatos.
- Gestión de usuarios administradores.
- Interfaz para consulta y exportación de datos vía API.
## Documentación de API
En el siguiente enlace se encuentra el archivo .yaml de la documentación de la API que puede ser visualizado con Swagger Editor:
https://uvmx-my.sharepoint.com/:u:/g/personal/zs22016079_estudiantes_uv_mx/EU0hmEyOj29LgbF9R8CStdcBVLkB11Ik2SfwYQg9fWKXtw?e=Kz3nci

View File

@ -0,0 +1,68 @@
<?php
require_once __DIR__ . "/../../config/Database.php";
if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['n'])){
$numero_inserts = intval($_GET['n']);
if ($numero_inserts <= 0) {
echo "El número de inserts debe ser un entero positivo.";
exit;
}
// Configuración de rangos para campos aleatorios
$id_examen_min = 1;
$id_examen_max = 15;
$id_tipo_id_min = 1;
$id_tipo_id_max = 5;
$id_rango_edad_min = 1;
$id_rango_edad_max = 7;
$id_genero_min = 1;
$id_genero_max = 3;
$conn = Database::getInstance();
$conn->begin_transaction();
try {
for ($i = 1; $i <= $numero_inserts; $i++) {
$nombres = "Nombre" . $i;
$primer_apellido = "ApellidoP" . $i;
$segundo_apellido = "ApellidoM" . $i;
$correo = "correo" . $i . "@gmail.com";
$fecha_entrada = date('Y-m-d H:i:s');
// fecha_salida es NULL por defecto en la tabla, o se puede especificar como NULL
$telefono = '';
for ($j = 0; $j < 10; $j++) {
$telefono .= rand(0, 9);
}
$id_examen = rand($id_examen_min, $id_examen_max);
$id_tipo_id = rand($id_tipo_id_min, $id_tipo_id_max);
$id_rango_edad = rand($id_rango_edad_min, $id_rango_edad_max);
$id_genero = rand($id_genero_min, $id_genero_max);
$sql = "INSERT INTO candidato (fecha_entrada, nombres, primer_apellido, segundo_apellido, correo, telefono, id_examen, id_tipo_id, id_rango_edad, id_genero)
VALUES ('{$fecha_entrada}', '{$nombres}', '{$primer_apellido}', '{$segundo_apellido}', '{$correo}', '{$telefono}', {$id_examen}, {$id_tipo_id}, {$id_rango_edad}, {$id_genero})";
if (!$conn->query($sql)) {
throw new Exception("Error al insertar el registro {$i}: " . $conn->error);
}
}
$conn->commit();
echo "Se han insertado {$numero_inserts} registros exitosamente.";
} catch (Exception $e) {
$conn->rollback();
echo "Error en la transacción: " . $e->getMessage();
} finally {
$conn->close();
}
} else {
echo "Parámetros incorrectos. Asegúrate de pasar 'n' como parámetro GET con un número entero. Por ejemplo: .../ruta/inserts-prueba.php?n=5";
}
?>

View File

@ -0,0 +1,102 @@
<?php
require_once __DIR__ . '/../models/CatalogosModel.php';
class CatalogosController {
private static $catalogosModel = null;
public static function inicializar() {
if (self::$catalogosModel === null) {
self::$catalogosModel = new Catalogos();
}
}
public static function obtenerInfiCodigoPostal($codigo_postal){
return self::$catalogosModel->obtenerInfiCodigoPostal($codigo_postal);
}
public static function obtenerGiros(){
return self::$catalogosModel->obtenerGiros();
}
public static function obtenerNombresExamenes(){
return self::$catalogosModel->obtenerNombresExamenes();
}
public static function obtenerTiposIdentificacion(){
return self::$catalogosModel->obtenerTiposIdentificacion();
}
public static function obtenerRangosEdad(){
return self::$catalogosModel->obtenerRangosEdad();
}
public static function obtenerNivelesEstudio(){
return self::$catalogosModel->obtenerNivelesEstudio();
}
public static function obtenerNombresEmpresasInstituciones(){
return self::$catalogosModel->obtenerNombresEmpresasInstituciones();
}
public static function obtenerMotivosExamen(){
return self::$catalogosModel->obtenerMotivosExamen();
}
}
CatalogosController::inicializar();
// Petición GET para información del código postal
if($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['codigo_postal'])) {
header('Content-Type: application/json');
$codigo_postal = $_GET['codigo_postal'];
$result = CatalogosController::obtenerInfiCodigoPostal($codigo_postal);
echo json_encode($result);
}
if($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['obtener']) && $_GET['obtener'] === 'giros') {
header('Content-Type: application/json');
$result = CatalogosController::obtenerGiros();
echo json_encode($result);
}
if($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['obtener']) && $_GET['obtener'] === 'examenes') {
header('Content-Type: application/json');
$result = CatalogosController::obtenerNombresExamenes();
echo json_encode($result);
}
if($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['obtener']) && $_GET['obtener'] === 'identificacion') {
header('Content-Type: application/json');
$result = CatalogosController::obtenerTiposIdentificacion();
echo json_encode($result);
}
if($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['obtener']) && $_GET['obtener'] === 'rangosedad') {
header('Content-Type: application/json');
$result = CatalogosController::obtenerRangosEdad();
echo json_encode($result);
}
if($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['obtener']) && $_GET['obtener'] === 'nivelesestudio') {
header('Content-Type: application/json');
$result = CatalogosController::obtenerNivelesEstudio();
echo json_encode($result);
}
if($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['obtener']) && $_GET['obtener'] === 'empresasinstituciones') {
header('Content-Type: application/json');
$result = CatalogosController::obtenerNombresEmpresasInstituciones();
echo json_encode($result);
}
if($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['obtener']) && $_GET['obtener'] === 'motivos') {
header('Content-Type: application/json');
$result = CatalogosController::obtenerMotivosExamen();
echo json_encode($result);
}
?>

View File

@ -0,0 +1,38 @@
<?php
require_once __DIR__ . '/../models/CandidatoModel.php';
class ControlCandidatos {
private $candidatoModel;
public function __construct() {
$this->candidatoModel = new CandidatoModel();
}
public function obtenerCandidatosSinFechaSalida(){
$resultado = $this->candidatoModel->obtenerCandidatosSinFechaSalida();
if( isset($resultado['estado'])){
return $resultado;
} else {
// iterar sobre $resultado y concatenar nombre completo, la fecha dejarla como esta
$candidatos = [];
foreach ($resultado as $candidato) {
$nombreCompleto = "{$candidato['nombres']} {$candidato['primer_apellido']} {$candidato['segundo_apellido']}";
$candidatos[] = [
'id_candidato' => $candidato['id_candidato'],
'nombre_completo' => $nombreCompleto,
'fecha_entrada' => $candidato['fecha_entrada']
];
}
return $candidatos;
}
}
public function eliminarCandidato($id_candidato){
return $this->candidatoModel->eliminarCandidato($id_candidato);
}
}
?>

View File

@ -0,0 +1,32 @@
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/UsuarioController.php';
// Inicializar el modelo de usuario
UsuarioController::inicializar();
if ( $_SERVER['REQUEST_METHOD'] === 'POST') {
$usuario = isset($_POST['numero-personal']) ? $_POST['numero-personal'] : '';
$contrasena = isset($_POST['contrasena']) ? $_POST['contrasena'] : '';
// Iniciar sesión y obtener respuesta
$respuesta = UsuarioController::iniciarSesion($usuario, $contrasena);
if ($respuesta['estado'] === 'exitoso') {
// Iniciar sesión
session_start();
$_SESSION['usuario'] = $usuario;
}
echo json_encode($respuesta);
} else {
// Metodo no permitido
echo json_encode([
'estado' => 'error',
'mensaje' => 'Método no permitido'
]);
}
?>

View File

@ -0,0 +1,16 @@
<?php
require_once __DIR__ . '/CandidatoController.php';
// Manejar la solicitud POST para registrar un candidato
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$insertId = CandidatoController::registrarCandidato();
if($insertId > 0){
echo json_encode(['registroExitoso' => true, 'message' => 'Se ha registrado el candidato correctamente.']);
} else {
echo json_encode(['registroExitoso' => false, 'message' => 'Error al registrar el candidato.']);
}
}
?>

View File

@ -0,0 +1,25 @@
<?php
require_once __DIR__ . '/CandidatoController.php';
// Manejar la solicitud POST para registrar información un candidato
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$respuesta = CandidatoController::registrarInfoCandidato();
if(isset($respuesta["estado"]) && $respuesta["estado"] === "exitoso") {
echo json_encode([
"estado" => "exitoso",
"mensaje" => "Se ha registrado la información correctamente.",
"res" => "id_candidato: " . $respuesta["mensaje"]
]);
} else if( isset($respuesta["estado"]) ){
echo json_encode([
"estado" => "error",
"mensaje" => "Error al registrar la información.",
"res" => $respuesta["mensaje"]
]);
}
}
?>

View File

@ -0,0 +1,56 @@
<?php
require_once __DIR__ . '/../models/UsuarioModel.php';
class UsuarioController{
private static $usuarioModel = null;
public static function inicializar() {
if (self::$usuarioModel === null) {
self::$usuarioModel = new UsuarioModel();
}
}
public static function obtenerUsuarios(){
return self::$usuarioModel->obtenerUsuarios();
}
public static function iniciarSesion($usuario, $contrasena){
return self::$usuarioModel->iniciarSesion($usuario, $contrasena);
}
public static function existeUsuario($usuario){
$usuario = self::$usuarioModel->buscarUsuario($usuario);
if ( isset($usuario['usuario']) ) {
return true;
} else {
return false;
}
}
public static function crearUsuario($usuario, $contrasena){
if ( self::existeUsuario($usuario) ) {
return [
'estado' => 'error',
'mensaje' => 'El usuario ya existe.'
];
} else {
return self::$usuarioModel->crearUsuario($usuario, $contrasena);
}
}
public static function eliminarUsuario($id_usuario){
return self::$usuarioModel->eliminarUsuario($id_usuario);
}
public static function actualizarUsuario($id, $usuario, $contrasena){
return self::$usuarioModel->actualizarUsuario($id, $usuario, $contrasena);
}
}
# Instanciar el modelo al cargar el controlador
UsuarioController::inicializar();
?>

View File

@ -0,0 +1,20 @@
<?php
require_once __DIR__ . '/UsuarioController.php';
header('Content-type: application/json');
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$id = $_POST['id'];
$usuario = $_POST['usuario'];
$contrasena = $_POST['contrasena'];
$resultado = UsuarioController::actualizarUsuario($id, $usuario, $contrasena);
echo json_encode($resultado);
} else {
http_response_code(400);
echo json_encode([
'estado' => 'error',
'mensaje' => 'Método no permitido.'
]);
}
?>

View File

@ -0,0 +1,53 @@
<?php
// filepath: c:\xampp\htdocs\Proyecto_Lania\LANIA_Proyecto\php\buscarCodigo.php
include 'conexionBd.php'; // Asegúrate de que el archivo esté en la ruta correcta
header('Content-Type: application/json'); // Aseguramos que la respuesta sea JSON
// Crear instancia de la conexión
$conexion = new Conexion();
$conexionBD = $conexion->conectar(); // Establecer conexión
// Verificar que se ha enviado el parámetro 'codigo_postal'
if (isset($_GET['codigo_postal'])) {
$codigo_postal = $_GET['codigo_postal'];
// Consulta SQL para obtener estado, ciudad y colonia en base al código postal
$sql = "SELECT e.nombre AS estado, m.nombre AS municipio, c.nombre AS colonia
FROM colonias c
JOIN municipios m ON c.municipio = m.id
JOIN estados e ON m.estado = e.id
WHERE c.codigo_postal = ?";
// Verificar que la conexión a la base de datos esté activa
if ($conexionBD) {
// Preparar la sentencia SQL
$stmt = $conexionBD->prepare($sql);
$stmt->bind_param("i", $codigo_postal); // El parámetro es un entero (i)
$stmt->execute();
// Obtener los resultados
$result = $stmt->get_result();
// Verificar si hay resultados
if ($result->num_rows > 0) {
$data = [];
while ($row = $result->fetch_assoc()) {
$data[] = $row; // Agregar cada resultado a un array
}
echo json_encode($data); // Devolver los resultados en formato JSON
} else {
echo json_encode([]); // Si no hay resultados, devolver un array vacío
}
$stmt->close();
} else {
echo json_encode(["error" => "Conexión a la base de datos fallida."]);
}
$conexionBD->close();
} else {
// Si no se recibe un código postal, respondemos con un error
echo json_encode(["error" => "Código postal no proporcionado."]);
}
?>

View File

@ -0,0 +1,4 @@
<?php
session_start();
session_destroy();
header('Location: ../index.html');

View File

@ -0,0 +1,19 @@
<?php
require_once __DIR__ . '/UsuarioController.php';
header('Content-type: application/json');
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$usuario = $_POST['usuario'];
$contrasena = $_POST['contrasena'];
$resultado = UsuarioController::crearUsuario($usuario, $contrasena);
echo json_encode($resultado);
} else {
http_response_code(400);
echo json_encode([
'estado' => 'error',
'mensaje' => 'Método no permitido.'
]);
}
?>

View File

@ -0,0 +1,18 @@
<?php
header('Content-Type: application/json', 'charset=UTF-8');
require_once __DIR__ . '/ControlCandidatos.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['id_candidato'])) {
$candidatoController = new ControlCandidatos();
$id_candidato = $_POST['id_candidato'];
$resultado = $candidatoController->eliminarCandidato($id_candidato);
echo json_encode($resultado);
} else {
http_response_code(400);
echo json_encode([
'estado' => 'error',
'mensaje' => 'Método no permitido.'
]);
}
?>

View File

@ -0,0 +1,18 @@
<?php
header('Content-Type: application/json', 'charset=UTF-8');
require_once __DIR__ . '/UsuarioController.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['id_usuario'])) {
$id_usuario = $_POST['id_usuario'];
$resultado = UsuarioController::eliminarUsuario($id_usuario);
echo json_encode($resultado);
} else {
http_response_code(400);
echo json_encode([
'estado' => 'error',
'mensaje' => 'Método no permitido.'
]);
}
?>

View File

@ -0,0 +1,59 @@
<?php
require_once '../config/Database.php';
require_once '../models/GraficoModel.php';
header('Content-Type: application/json'); // Aseguramos que siempre se devuelva JSON
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
$tipoConsulta = $input['tipoConsulta'] ?? '';
switch ($tipoConsulta) {
case 'Femenino':
$cantidad = GraficoModel::obtenerGeneroFemenino();
break;
case 'Masculino':
$cantidad = GraficoModel::obtenerGeneroMasculino();
break;
case 'Prefiero no decirlo':
$cantidad = GraficoModel::obtenerGeneroNoDefinido();
break;
case 'Menor de 18 años':
$cantidad = GraficoModel::obtenerEdadMenor18();
break;
case '18 a 24 años':
$cantidad = GraficoModel::obtenerEdad1824();
break;
case '25 a 34 años':
$cantidad = GraficoModel::obtenerEdad2434();
break;
case '35 a 44 años':
$cantidad = GraficoModel::obtenerEdad3544();
break;
case '45 a 54 años':
$cantidad = GraficoModel::obtenerEdad4554();
break;
case '55 a 64 años':
$cantidad = GraficoModel::obtenerEdad5564();
break;
case '65 años o más':
$cantidad = GraficoModel::obtenerEdad65oMas();
break;
case 'Estados':
$cantidad = GraficoModel::obtenerEstados();
echo json_encode($cantidad); // Devolver directamente el array de estados
exit; // Terminar la ejecucion aqui
case 'Examenes':
$cantidad = GraficoModel::obtenerExamenes();
echo json_encode($cantidad); // Devolver directamente el array de examenes
exit; // Terminar la ejecucion aqui
default:
$cantidad = 0;
}
echo json_encode(['cantidad' => $cantidad]);
}
?>

View File

@ -0,0 +1 @@
<?php

38
css/consultar-api.css Normal file
View File

@ -0,0 +1,38 @@
.token-info {
word-break: break-all;
background-color: #f8f9fa;
padding: 15px;
border-radius: 5px;
margin-bottom: 20px;
}
pre {
background-color: #f8f9fa;
padding: 15px;
border-radius: 5px;
}
.hidden {
display: none;
}
.success-message {
background-color: #d4edda;
border-color: #c3e6cb;
color: #155724;
padding: 10px;
border-radius: 5px;
margin-bottom: 15px;
}
.download-section {
background-color: #f8f9fa;
padding: 15px;
border-radius: 5px;
margin-bottom: 20px;
border: 1px solid #dee2e6;
}
.download-buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.download-btn {
min-width: 120px;
}

132
css/login.css Normal file
View File

@ -0,0 +1,132 @@
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Open Sans', sans-serif;
}
body {
background-color: #f1f0f6;
display: grid;
place-items: center;
min-height: 100vh;
}
.login-container {
background-color: #ffffff;
border: 2px solid #e8e8e8;
border-radius: 8px;
padding: 2rem;
width: 100%;
max-width: 500px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
@media screen and (max-width: 768px) {
.login-container {
width: 80%;
}
}
.login-encabezado {
text-align: center;
margin-bottom: 1.5rem;
}
.login-logo {
max-width: 150px;
height: auto;
margin-bottom: 1rem;
}
.login-titulo {
font-size: 1.5rem;
color: #333;
display: flex;
align-items: center;
justify-content: center;
}
.login-subtitulo {
color: #666;
margin-top: 0.5rem;
}
.login-formulario {
color: #666;
margin-top: 0.5rem;
}
.form-grupo {
margin-bottom: 1rem;
}
.form-label {
display: flex;
align-items: center;
margin-bottom: 0.5rem;
font-weight: bold;
}
.form-input {
width: 100%;
padding: 0.75rem;
border: 1px solid #e8e8e8;
border-radius: 4px;
background-color: #f6f6f6;
transition: all 0.3s ease;
}
.form-input:focus {
outline: none;
border-color: #35245b;
box-shadow: 0 0 0 2px rgba(2, 27, 48, 0.2);
}
.login-boton {
background-color: #35245b;
color: white;
border: none;
border-radius: 4px;
padding: 0.75rem;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 1rem;
width: 100%;
}
.login-boton:hover {
background-color: #432d74;
box-shadow: 0 4px 8px #53388f;
}
.login-boton:active {
transform: scale(0.98);
}
.material-icons {
font-size: 1.25rem;
}
.mensaje-error {
display: none;
background-color: #f8d7da;
color: #dc3545;
border: 1px solid #dc3545;
border-radius: 4px;
padding: 0.75rem;
margin-top: 1rem;
text-align: center;
animation: fadeIn 0.3s ease-in-out;
}

2106
font/bootstrap-icons.css vendored Normal file

File diff suppressed because it is too large Load Diff

5
font/bootstrap-icons.min.css vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

260
form_datos_extendidos.html Normal file
View File

@ -0,0 +1,260 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href='https://unpkg.com/boxicons@2.0.9/css/boxicons.min.css' rel='stylesheet'>
<link rel="stylesheet" href="css/form.css">
<title>AdminSite</title>
</head>
<body>
<!-- SIDEBAR -->
<section id="sidebar">
<a href="inicio.html" class="brand"><i class='bx bx-code-alt icon' ></i> LANIA</a>
<!-- <a href="#" class="brand">
<img src="img/lania_logo.png" alt="Logo" style="height: 40px;">
</a> -->
<ul class="side-menu">
<li><a href="#" class="active"><i class='bx bxs-dashboard icon' ></i> Dashboard</a></li>
<li class="divider" data-text="main">Main</li>
<li>
<a href="#"><i class='bx bxs-inbox icon' ></i> Elements <i class='bx bx-chevron-right icon-right' ></i></a>
<ul class="side-dropdown">
<li><a href="#">Alert</a></li>
<li><a href="#">Badges</a></li>
<li><a href="#">Breadcrumbs</a></li>
<li><a href="#">Button</a></li>
</ul>
</li>
<li><a href="#"><i class='bx bxs-chart icon' ></i> Charts</a></li>
<li><a href="#"><i class='bx bxs-widget icon' ></i> Widgets</a></li>
<li class="divider" data-text="table and forms">Table and forms</li>
<li><a href="#"><i class='bx bx-table icon' ></i> Tables</a></li>
<li>
<a href="#"><i class='bx bxs-notepad icon' ></i> Formularios <i class='bx bx-chevron-right icon-right' ></i></a>
<ul class="side-dropdown">
<li><a href="form_datos_basicos.html">Datos Básicos</a></li>
<li><a href="form_datos_extendidos.html">Datos Extendidos</a></li>
</ul>
</li>
</ul>
<!-- <div class="ads">
<div class="wrapper">
<a href="#" class="btn-upgrade">Upgrade</a>
<p>Become a <span>PRO</span> member and enjoy <span>All Features</span></p>
</div>
</div> -->
</section>
<!-- SIDEBAR -->
<!-- NAVBAR -->
<section id="content">
<!-- NAVBAR -->
<nav>
<i class='bx bx-menu toggle-sidebar' ></i>
<form action="#">
<div class="form-group">
<input type="text" placeholder="Buscar...">
<i class='bx bx-search icon' ></i>
</div>
</form>
<a href="#" class="nav-link">
<i class='bx bxs-bell icon' ></i>
<span class="badge">5</span>
</a>
<a href="#" class="nav-link">
<i class='bx bxs-message-square-dots icon' ></i>
<span class="badge">8</span>
</a>
<span class="divider"></span>
<div class="profile">
<img src="https://images.unsplash.com/photo-1517841905240-472988babdf9?ixid=MnwxMjA3fDB8MHxzZWFyY2h8NHx8cGVvcGxlfGVufDB8fDB8fA%3D%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60" alt="">
<ul class="profile-link">
<li><a href="#"><i class='bx bxs-user-circle icon' ></i> Profile</a></li>
<li><a href="#"><i class='bx bxs-cog' ></i> Settings</a></li>
<li><a href="#"><i class='bx bxs-log-out-circle' ></i> Logout</a></li>
</ul>
</div>
</nav>
<!-- NAVBAR -->
<!-- MAIN -->
<main>
<h1 class="title">Datos Extendidos</h1>
<ul class="breadcrumbs">
<li><a href="#">Formularios</a></li>
<li class="divider">/</li>
<li><a href="#" class="active">Datos Extendidos</a></li>
</ul>
<div class="form-container">
<form id="formulario-datos-extendido" method="post" action="#">
<div class="form-section">
<br>
<h3>Ubicación</h3>
<br>
<div class="form-row">
<input type="hidden" id="id_candidato" name="id_candidato">
<div class="form-col">
<div class="form-group">
<label for="id_pais" class="required">País</label>
<select id="id_pais" name="id_pais" required>
<option value="">Seleccione...</option>
<!-- Aquí irían los países desde la base de datos -->
<option value="1">México</option>
<option value="2">Otro</option>
</select>
</div>
</div>
<div class="form-col">
<div class="form-group">
<label for="codigo_postal" class="required">Código Postal</label>
<input type="number" min="0" step="1" id="codigo_postal" name="codigo_postal" maxlength="10" required>
<button type="button" id="buscarBtn"><span class="material-icons">search</span>Buscar</button>
</div>
</div>
<div class="form-col">
<div class="form-group">
<label for="id_estado">Estado</label>
<select id="id_estado" name="id_estado">
<option value="">Seleccione...</option>
<!-- Aquí irían los estados desde la base de datos -->
</select>
</div>
</div>
</div>
<div class="form-row">
<div class="form-col">
<div class="form-group">
<label for="id_municipio">Municipio</label>
<select id="id_municipio" name="id_municipio">
<option value="">Seleccione...</option>
<!-- Aquí irían los municipios desde la base de datos -->
</select>
</div>
</div>
<div class="form-col">
<div class="form-group">
<label for="id_colonia">Colonia</label>
<select id="id_colonia" name="id_colonia">
<option value="">Seleccione...</option>
<!-- Aquí irían las colonias desde la base de datos -->
</select>
</div>
</div>
</div>
</div>
<!-- Sección 3: Información Académica y Laboral -->
<div class="form-section">
<br>
<h3>Información Académica/Laboral</h3>
<br>
<div class="form-row">
<div class="form-col">
<div class="form-group">
<label for="id_nivel" class="required">Nivel de Estudios</label>
<select id="id_nivel" name="id_nivel" required>
<option value="">Seleccione...</option>
<option value="1">Primaria</option>
<option value="2">Secundaria</option>
<option value="3">Bachillerato</option>
<option value="4">Técnico Superior Universitario</option>
<option value="5">Licenciatura</option>
<option value="6">Maestría</option>
<option value="7">Doctorado</option>
<option value="8">Otro</option>
</select>
</div>
</div>
<div class="form-col">
<div class="form-group">
<label for="id_giro" class="required">Giro</label>
<select id="id_giro" name="id_giro" required>
<option value="">Seleccione...</option>
<option value="1">Tecnologías de la Información</option>
<option value="2">Gobierno</option>
<option value="3">Finanzas</option>
<option value="4">Salud</option>
<option value="5">Educación</option>
<option value="6">Telecomunicaciones</option>
<option value="7">Retail</option>
<option value="8">Manufactura</option>
<option value="9">Logística y Transporte</option>
<option value="10">Construcción</option>
<option value="11">Turismo y Hospitalidad</option>
<option value="12">Energía y Recursos Naturales</option>
<option value="13">Agricultura y Alimentación</option>
<option value="14">Medios de Comunicación y Entretenimiento</option>
<option value="15">Otros</option>
</select>
</div>
</div>
</div>
<div class="form-group">
<label for="nombre_empresa">Nombre de la Empresa/Institución</label>
<input type="text" id="nombre_empresa" name="nombre_empresa"
maxlength="100">
</div>
<div class="form-group">
<label for="motivo_examen">Motivo del Examen</label>
<textarea id="motivo_examen" name="motivo_examen" rows="3"></textarea>
</div>
</div>
<!-- Sección 4: Evaluación y Consentimiento -->
<div class="form-section">
<br>
<h3>Evaluación y Consentimiento</h3>
<br>
<div class="form-group">
<p>Calificación del Servicio</p>
<br>
<label for="calificacion_servicio">¿Qué tan satisfecho estás con nuestra institución en general, considerando la calidad de las instalaciones, la atención del personal y el proceso de certificación?</label>
<br>
<select id="calificacion_servicio" name="calificacion_servicio" class="select-emoji">
<option value="">Seleccione una calificación...</option>
<option value="1">😡 Muy insatisfecho</option>
<option value="2">😕 Insatisfecho</option>
<option value="3">😐 Neutral</option>
<option value="4">🙂 Satisfecho</option>
<option value="5">😄 Muy satisfecho</option>
</select>
</div>
<br>
<div class="form-group">
<input type="checkbox" id="consentimiento_pub" name="consentimiento_pub" value="1" checked>
<label for="consentimiento_pub" style="display: inline;">Doy mi consentimiento para la publicación de mis datos</label>
</div>
</div>
<br>
<button type="submit" class="btn-submit">Enviar</button>
<div id="mensaje-error" class="mensaje-error"></div>
</form>
</div>
</main>
<!-- MAIN -->
</section>
<!-- NAVBAR -->
<script src="js/form.js"></script>
<script src="js/form_datos_extendidos.js"></script>
<script src="https://website-widgets.pages.dev/dist/sienna.min.js" defer></script>
<script src="js/buscarCodigo.js"></script>
</body>
</html>

69
js/actualizar-usuario.js Normal file
View File

@ -0,0 +1,69 @@
document.addEventListener('DOMContentLoaded', function(){
form = document.getElementById('form');
const contrasena = document.getElementById('contrasena');
const contrasenaVerificacion = document.getElementById('contrasena_verificacion');
const btnSubmit = document.getElementById('submit');
const togglePassword = document.getElementById('togglePassword');
togglePassword.addEventListener('click', function(e) {
const type = contrasena.getAttribute('type') === 'password' ? 'text' : 'password';
contrasena.setAttribute('type', type);
const icon = togglePassword.querySelector('i');
if (type === 'text') {
icon.classList.remove('bi-eye');
icon.classList.add('bi-eye-slash');
} else {
icon.classList.remove('bi-eye-slash');
icon.classList.add('bi-eye');
}
}
);
btnSubmit.addEventListener('click', function(event){
if (!form.checkValidity()) {
form.reportValidity();
return;
}
event.preventDefault();
const c1 = contrasena.value;
const c2 = contrasenaVerificacion.value;
if (c1 === c2) {
const usuario = document.getElementById('usuario').value;
const regex = /^[a-zA-Z0-9]+$/;
if (!regex.test(usuario)) {
alert("Nombre de usuario no valido.");
return;
}
const id = document.getElementById('id').value;
const data = new FormData();
data.append("id", id);
data.append("usuario", usuario);
data.append("contrasena", c1);
fetch('../controllers/actualizarUsuario.php', {
method: 'POST',
body: data
})
.then(response => response.json())
.then(data => {
if (data.estado === "exitoso") {
alert(data.mensaje);
} else if (data.estado === "error") {
alert("Error, intentelo más tarde.");
console.log(data.mensaje);
} else {
alert("Error inesperado, intentelo más tarde.");
console.log(data);
}
window.location.href = "control-usuarios.php";
})
} else {
alert("Las contraseñas no coinciden");
}
})
})

77
js/buscarCodigo.js Normal file
View File

@ -0,0 +1,77 @@
document.addEventListener("DOMContentLoaded", function() {
var buscarBtn = document.getElementById("buscarBtn");
//Añadir un evento al boton de buscar
buscarBtn.addEventListener("click", function() {
var codigoPostal = document.getElementById("codigo_postal").value;
console.log(codigoPostal);
//verificar si el codigo postal esta vacio
if (codigoPostal) {
//crear la url para la peticion
var url = "./php/buscarCodigo.php?codigo_postal=" + encodeURIComponent(codigoPostal);
//Realiza una solicitud HTTP GET a la URL especificada
fetch(url)
.then(response => {
//console.log("Estado de la respuesta:", response.status);
//console.log("Contenido de la respuesta:", response);
// Verifica si la respuesta del servidor es exitosa
if (!response.ok) {
throw new Error("Error en la respuesta del servidor");
}
//Convierte la respuesta a formato JSON
return response.json();
})
.then(data => {
var estadoSelect = document.getElementById("id_estado");
var municipioSelect = document.getElementById("id_municipio");
var coloniaSelect = document.getElementById("id_colonia");
// Inicializa los elementos select con una opción por defecto
estadoSelect.innerHTML = "<option value=''>Seleccionar Estado</option>";
municipioSelect.innerHTML = "<option value=''>Seleccionar Municipios</option>";
coloniaSelect.innerHTML = "<option value=''>Seleccionar Colonia</option>";
// Verifica si se recibieron datos
if (data.length > 0) {
//Crea conjuntos para almacenar estados, ciudades y colonias únicos
let estados = new Set();
let municipios = new Set();
let colonias = [];
// Itera sobre cada fila de datos recibidos
data.forEach(row => {
estados.add(row.estado);
municipios.add(row.municipio);
colonias.push(row.colonia);
});
//Añade las opciones de estados al elemento select
estados.forEach(estado => {
estadoSelect.innerHTML += "<option value='" + estado + "'>" + estado + "</option>";
});
//Añade las opciones de ciudades al elemento select
municipios.forEach(municipio => {
municipioSelect.innerHTML += "<option value='" + municipio + "'>" + municipio + "</option>";
});
//Añade las opciones de colonias al elemento select
colonias.forEach(colonia => {
coloniaSelect.innerHTML += "<option value='" + colonia + "'>" + colonia + "</option>";
});
} else {
alert("No se encontraron datos para el código postal ingresado.");
}
})
.catch(error => {
console.error("Error en la solicitud:", error);
});
} else {
alert("Por favor, ingrese un código postal.");
}
});
});

34
js/control-usuario.js Normal file
View File

@ -0,0 +1,34 @@
document.addEventListener("DOMContentLoaded" , function() {
const tabla = document.querySelector('table');
tabla.addEventListener("click", function(evento) {
const botonEliminar = evento.target.closest(".boton-eliminar");
if (botonEliminar) {
const idUsuarioEliminar = botonEliminar.dataset.idUsuario;
const nombreUsuarioEliminar = botonEliminar.dataset.nombreUsuario;
// Mostrar un mensaje de confirmación antes de eliminar
if (!confirm("¿Desea eliminar al usuario " + nombreUsuarioEliminar + "?")) {
return;
}
const data = new FormData();
data.append("id_usuario", idUsuarioEliminar);
fetch('../controllers/eliminarUsuario.php', {
method: 'POST',
body: data
})
.then(response => response.json())
.then(data => {
alert(data.mensaje);
location.reload();
})
.catch((error) => {
console.error('Error:', error);
});
}
});
});

69
js/crear-usuario.js Normal file
View File

@ -0,0 +1,69 @@
document.addEventListener('DOMContentLoaded', function(){
const form = document.getElementById('form');
const contrasena = document.getElementById('contrasena');
const contrasenaVerificacion = document.getElementById('contrasena_verificacion');
const btnSubmit = document.getElementById('submit');
const togglePassword = document.getElementById('togglePassword');
const password = document.getElementById('contrasena');
togglePassword.addEventListener('click', function(e) {
const type = password.getAttribute('type') === 'password' ? 'text' : 'password';
password.setAttribute('type', type);
const icon = togglePassword.querySelector('i');
if (type === 'text') {
icon.classList.remove('bi-eye');
icon.classList.add('bi-eye-slash');
} else {
icon.classList.remove('bi-eye-slash');
icon.classList.add('bi-eye');
}
}
);
btnSubmit.addEventListener('click', function(event){
// Forzar vaidación nativa de HTML5
if (!form.checkValidity()) {
form.reportValidity();
return;
}
event.preventDefault();
const c1 = contrasena.value;
const c2 = contrasenaVerificacion.value;
if (c1 === c2) {
const usuario = document.getElementById('usuario').value;
const regex = /^[a-zA-Z0-9]+$/;
if (!regex.test(usuario)) {
alert("Nombre de usuario no valido.");
return;
}
const data = new FormData();
data.append("usuario", usuario);
data.append("contrasena", c1);
fetch('../controllers/crearUsuario.php', {
method: 'POST',
body: data
})
.then(response => response.json())
.then(data => {
if (data.estado === "exitoso") {
alert(data.mensaje);
window.location.href = "control-usuarios.php";
} else if (data.estado === "error") {
alert("Error, intentelo más tarde.");
console.log(data.mensaje);
} else {
alert("Error inesperado, intentelo más tarde.");
console.log(data);
}
})
} else {
alert("Las contraseñas no coinciden");
}
})
})

148
js/form.js Normal file
View File

@ -0,0 +1,148 @@
// SIDEBAR DROPDOWN
const allDropdown = document.querySelectorAll('#sidebar .side-dropdown');
const sidebar = document.getElementById('sidebar');
allDropdown.forEach(item=> {
const a = item.parentElement.querySelector('a:first-child');
a.addEventListener('click', function (e) {
e.preventDefault();
if(!this.classList.contains('active')) {
allDropdown.forEach(i=> {
const aLink = i.parentElement.querySelector('a:first-child');
aLink.classList.remove('active');
i.classList.remove('show');
})
}
this.classList.toggle('active');
item.classList.toggle('show');
})
})
// SIDEBAR COLLAPSE
const toggleSidebar = document.querySelector('nav .toggle-sidebar');
const allSideDivider = document.querySelectorAll('#sidebar .divider');
if(sidebar.classList.contains('hide')) {
allSideDivider.forEach(item=> {
item.textContent = '-'
})
allDropdown.forEach(item=> {
const a = item.parentElement.querySelector('a:first-child');
a.classList.remove('active');
item.classList.remove('show');
})
} else {
allSideDivider.forEach(item=> {
item.textContent = item.dataset.text;
})
}
toggleSidebar.addEventListener('click', function () {
sidebar.classList.toggle('hide');
if(sidebar.classList.contains('hide')) {
allSideDivider.forEach(item=> {
item.textContent = '-'
})
allDropdown.forEach(item=> {
const a = item.parentElement.querySelector('a:first-child');
a.classList.remove('active');
item.classList.remove('show');
})
} else {
allSideDivider.forEach(item=> {
item.textContent = item.dataset.text;
})
}
})
sidebar.addEventListener('mouseleave', function () {
if(this.classList.contains('hide')) {
allDropdown.forEach(item=> {
const a = item.parentElement.querySelector('a:first-child');
a.classList.remove('active');
item.classList.remove('show');
})
allSideDivider.forEach(item=> {
item.textContent = '-'
})
}
})
sidebar.addEventListener('mouseenter', function () {
if(this.classList.contains('hide')) {
allDropdown.forEach(item=> {
const a = item.parentElement.querySelector('a:first-child');
a.classList.remove('active');
item.classList.remove('show');
})
allSideDivider.forEach(item=> {
item.textContent = item.dataset.text;
})
}
})
// PROFILE DROPDOWN
const profile = document.querySelector('nav .profile');
const imgProfile = profile.querySelector('img');
const dropdownProfile = profile.querySelector('.profile-link');
imgProfile.addEventListener('click', function () {
dropdownProfile.classList.toggle('show');
})
// MENU
const allMenu = document.querySelectorAll('main .content-data .head .menu');
allMenu.forEach(item=> {
const icon = item.querySelector('.icon');
const menuLink = item.querySelector('.menu-link');
icon.addEventListener('click', function () {
menuLink.classList.toggle('show');
})
})
window.addEventListener('click', function (e) {
if(e.target !== imgProfile) {
if(e.target !== dropdownProfile) {
if(dropdownProfile.classList.contains('show')) {
dropdownProfile.classList.remove('show');
}
}
}
allMenu.forEach(item=> {
const icon = item.querySelector('.icon');
const menuLink = item.querySelector('.menu-link');
if(e.target !== icon) {
if(e.target !== menuLink) {
if (menuLink.classList.contains('show')) {
menuLink.classList.remove('show')
}
}
}
})
})

43
js/login.js Normal file
View File

@ -0,0 +1,43 @@
const formulario = document.getElementById("login-formulario");
const notificacion = document.getElementById("mensaje-error");
formulario.addEventListener("submit", async (event) => {
event.preventDefault();
const numeroPersonal = document.getElementById("numero-personal").value;
const contrasena = document.getElementById("contrasena").value;
const data = new FormData();
data.append("numero-personal", numeroPersonal);
data.append("contrasena", contrasena);
try {
const respuestaPeticion = await fetch("controllers/LoginController.php", {
method: "POST",
body: data,
});
// const respuesta = await respuestaPeticion.json();
const respuesta = await respuestaPeticion.json();
if (respuesta.estado === 'exitoso') {
window.location.href = 'views/inicio.html';
} else if(respuesta.estado === 'error') {
notificacion.textContent = respuesta.mensaje;
if(respuesta.res){ // Si existe respuesta.res hubo un error y se imprime en consola
console.error(respuesta.res)
}
notificacion.style.display = "block";
} else {
notificacion.textContent = "Lo sentimos, el servicio no está disponible por el momento";
console.error("No se recibió la respuesta esperada del servidor");
}
} catch (error) {
notificacion.textContent = "Lo sentimos, el servicio no está disponible por el momento";
console.error(error)
}
});

160
js/sidebar-navbar.js Normal file
View File

@ -0,0 +1,160 @@
// SIDEBAR DROPDOWN
const allDropdown = document.querySelectorAll('#sidebar .side-dropdown');
const sidebar = document.getElementById('sidebar');
allDropdown.forEach(item=> {
const a = item.parentElement.querySelector('a:first-child');
a.addEventListener('click', function (e) {
e.preventDefault();
if(!this.classList.contains('active')) {
allDropdown.forEach(i=> {
const aLink = i.parentElement.querySelector('a:first-child');
aLink.classList.remove('active');
i.classList.remove('show');
})
}
this.classList.toggle('active');
item.classList.toggle('show');
})
})
// SIDEBAR COLLAPSE
// const toggleSidebar = document.querySelector('nav .toggle-sidebar');
const allSideDivider = document.querySelectorAll('#sidebar .divider');
if(sidebar.classList.contains('hide')) {
allSideDivider.forEach(item=> {
item.textContent = '-'
})
allDropdown.forEach(item=> {
const a = item.parentElement.querySelector('a:first-child');
a.classList.remove('active');
item.classList.remove('show');
})
} else {
allSideDivider.forEach(item=> {
item.textContent = item.dataset.text;
})
}
// toggleSidebar.addEventListener('click', function () {
// sidebar.classList.toggle('hide');
//
// if(sidebar.classList.contains('hide')) {
// allSideDivider.forEach(item=> {
// item.textContent = '-'
// })
//
// allDropdown.forEach(item=> {
// const a = item.parentElement.querySelector('a:first-child');
// a.classList.remove('active');
// item.classList.remove('show');
// })
// } else {
// allSideDivider.forEach(item=> {
// item.textContent = item.dataset.text;
// })
// }
// })
sidebar.addEventListener('mouseleave', function () {
if(this.classList.contains('hide')) {
allDropdown.forEach(item=> {
const a = item.parentElement.querySelector('a:first-child');
a.classList.remove('active');
item.classList.remove('show');
})
allSideDivider.forEach(item=> {
item.textContent = '-'
})
}
})
sidebar.addEventListener('mouseenter', function () {
if(this.classList.contains('hide')) {
allDropdown.forEach(item=> {
const a = item.parentElement.querySelector('a:first-child');
a.classList.remove('active');
item.classList.remove('show');
})
allSideDivider.forEach(item=> {
item.textContent = item.dataset.text;
})
}
})
// PROFILE DROPDOWN
// const profile = document.querySelector('nav .profile');
// const imgProfile = profile.querySelector('img');
// const dropdownProfile = profile.querySelector('.profile-link');
//
// imgProfile.addEventListener('click', function () {
// dropdownProfile.classList.toggle('show');
// })
// MENU
const allMenu = document.querySelectorAll('main .content-data .head .menu');
allMenu.forEach(item=> {
const icon = item.querySelector('.icon');
const menuLink = item.querySelector('.menu-link');
icon.addEventListener('click', function () {
menuLink.classList.toggle('show');
})
})
// window.addEventListener('click', function (e) {
// if(e.target !== imgProfile) {
// if(e.target !== dropdownProfile) {
// if(dropdownProfile.classList.contains('show')) {
// dropdownProfile.classList.remove('show');
// }
// }
// }
//
// allMenu.forEach(item=> {
// const icon = item.querySelector('.icon');
// const menuLink = item.querySelector('.menu-link');
//
// if(e.target !== icon) {
// if(e.target !== menuLink) {
// if (menuLink.classList.contains('show')) {
// menuLink.classList.remove('show')
// }
// }
// }
// })
// })
// // PROGRESSBAR
// const allProgress = document.querySelectorAll('main .card .progress');
//
// allProgress.forEach(item=> {
// item.style.setProperty('--value', item.dataset.value)
// })

155
models/CatalogosModel.php Normal file
View File

@ -0,0 +1,155 @@
<?php
require_once __DIR__ . '/../config/Database.php';
class Catalogos{
private $conn;
public function __construct() {
$this->conn = Database::getInstance();
}
/**
* Obtiene el estado, municipio y colonia en base al código postal proporcionado.
* @param int $codigo_postal El código postal a buscar.
* @return array Un array con los datos de estado, municipio y colonia.
*/
public function obtenerInfiCodigoPostal($codigo_postal){
// Consulta SQL para obtener estado, ciudad y colonia en base al código postal
$sql = "SELECT e.id AS id_estado, e.nombre AS estado, m.id AS id_municipio, m.nombre AS municipio, c.id AS id_colonia, c.nombre AS colonia
FROM colonias c
JOIN municipios m ON c.municipio = m.id
JOIN estados e ON m.estado = e.id
WHERE c.codigo_postal = ?";
// Preparar la sentencia SQL
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("i", $codigo_postal);
$stmt->execute();
// Obtener los resultados
$result = $stmt->get_result();
$stmt->close();
$data = [];
// Verificar si hay resultados
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$data[] = $row; // Agregar cada resultado a un array
}
return $data;
} else {
return $data;
}
}
/**
* Obtiene los giros.
* @return array
*/
public function obtenerGiros(){
$sql = "SELECT id_giro, descripcion FROM giro ORDER BY descripcion";
$result = $this->conn->query($sql);
$giros = [];
while($row = $result->fetch_assoc()){
$giros[] = $row;
}
return $giros;
}
/**
* Obtiene los nombres de los examenes disponibles.
* @return array Lista del nombre de la institución a la que pertenece el examen.
*/
public function obtenerNombresExamenes(){
$sql = "SELECT id_examen, nombre_examen FROM examen ORDER BY nombre_examen";
$result = $this->conn->query($sql);
$examenes = [];
while($row = $result->fetch_assoc()){
$examenes[] = $row;
}
return $examenes;
}
/**
* Obtener los tipos de identificación.
* @return array
*/
public function obtenerTiposIdentificacion(){
$sql = "SELECT id_tipo_id, descripcion FROM tipo_identificacion ORDER BY descripcion";
$result = $this->conn->query($sql);
$tipos = [];
while($row = $result->fetch_assoc()){
$tipos[] = $row;
}
return $tipos;
}
/**
* Obtiene los rangos de edad.
* @return array
*/
public function obtenerRangosEdad(){
$sql = "SELECT id_rango_edad, descripcion FROM rango_edad ORDER BY id_rango_edad";
$result = $this->conn->query($sql);
$rangos = [];
while($row = $result->fetch_assoc()){
$rangos[] = $row;
}
return $rangos;
}
/**
* Obtiene los niveles de estudio.
* @return array
*/
public function obtenerNivelesEstudio(){
$sql = "SELECT id_nivel, descripcion FROM nivel_estudio ORDER BY id_nivel";
$result = $this->conn->query($sql);
$niveles = [];
while($row = $result->fetch_assoc()){
$niveles[] = $row;
}
return $niveles;
}
public function obtenerNombresEmpresasInstituciones(){
$sql = "SELECT DISTINCT nombre_empresa_institucion FROM info_candidatos ORDER BY nombre_empresa_institucion";
$result = $this->conn->query($sql);
$empresas = [];
while($row = $result->fetch_assoc()){
$empresas[] = $row;
}
return $empresas;
}
public function obtenerMotivosExamen(){
$sql = "SELECT id, descripcion FROM motivo_examen ORDER BY descripcion";
$result = $this->conn->query($sql);
$motivos = [];
while($row = $result->fetch_assoc()){
$motivos[] = $row;
}
return $motivos;
}
}
?>

266
models/GraficoModel.php Normal file
View File

@ -0,0 +1,266 @@
<?php
require_once '../config/Database.php';
class GraficoModel{
private $db;
public function __construct() {
$db = DataBase::getInstance();
}
public static function obtenerGeneroFemenino() {
$query = self::$db->prepare("SELECT COUNT(*) AS Femenino FROM candidato WHERE id_genero = 2 ");
$query->execute();
$resultado = $query->get_result();
$output = "0";
if($resultado->num_rows > 0) {
while ($data = $resultado->fetch_assoc()) {
$output= $data['Femenino'];
}
}
$query->close();
self::$db->close();
return $output;
}
public static function obtenerGeneroMasculino() {
$query = self::$db->prepare("SELECT COUNT(*) AS Maculino FROM candidato WHERE id_genero = 1 ");
$query->execute();
$resultado = $query->get_result();
$output = "0";
if($resultado->num_rows > 0) {
while ($data = $resultado->fetch_assoc()) {
$output= $data['Maculino'];
}
}
$query->close();
self::$db->close();
return $output;
}
public static function obtenerGeneroNoDefinido() {
$query = self::$db->prepare("SELECT COUNT(*) AS NoDefinido FROM candidato WHERE id_genero = 3 ");
$query->execute();
$resultado = $query->get_result();
$output = "0";
if($resultado->num_rows > 0) {
while ($data = $resultado->fetch_assoc()) {
$output= $data['NoDefinido'];
}
}
$query->close();
self::$db->close();
return $output;
}
public static function obtenerEdadMenor18() {
$query = self::$db->prepare("SELECT COUNT(*) AS menorEdad FROM candidato WHERE id_rango_edad = 1");
$query->execute();
$resultado = $query->get_result();
$output = "0";
if($resultado->num_rows > 0) {
while ($data = $resultado->fetch_assoc()) {
$output= $data['menorEdad'];
}
}
$query->close();
self::$db->close();
return $output;
}
public static function obtenerEdad1824() {
$query = self::$db->prepare("SELECT COUNT(*) AS edad1824 FROM candidato WHERE id_rango_edad = 2");
$query->execute();
$resultado = $query->get_result();
$output = "0";
if($resultado->num_rows > 0) {
while ($data = $resultado->fetch_assoc()) {
$output= $data['edad1824'];
}
}
$query->close();
self::$db->close();
return $output;
}
public static function obtenerEdad2434() {
$query = self::$db->prepare("SELECT COUNT(*) AS edad2434 FROM candidato WHERE id_rango_edad = 3");
$query->execute();
$resultado = $query->get_result();
$output = "0";
if($resultado->num_rows > 0) {
while ($data = $resultado->fetch_assoc()) {
$output= $data['edad2434'];
}
}
$query->close();
self::$db->close();
return $output;
}
public static function obtenerEdad3544() {
$query = self::$db->prepare("SELECT COUNT(*) AS edad3544 FROM candidato WHERE id_rango_edad = 4");
$query->execute();
$resultado = $query->get_result();
$output = "0";
if($resultado->num_rows > 0) {
while ($data = $resultado->fetch_assoc()) {
$output= $data['edad3544'];
}
}
$query->close();
self::$db->close();
return $output;
}
public static function obtenerEdad4554() {
$query = self::$db->prepare("SELECT COUNT(*) AS edad4554 FROM candidato WHERE id_rango_edad = 5");
$query->execute();
$resultado = $query->get_result();
$output = "0";
if($resultado->num_rows > 0) {
while ($data = $resultado->fetch_assoc()) {
$output= $data['edad4554'];
}
}
$query->close();
self::$db->close();
return $output;
}
public static function obtenerEdad5564() {
$query = self::$db->prepare("SELECT COUNT(*) AS edad5564 FROM candidato WHERE id_rango_edad = 6");
$query->execute();
$resultado = $query->get_result();
$output = "0";
if($resultado->num_rows > 0) {
while ($data = $resultado->fetch_assoc()) {
$output= $data['edad5564'];
}
}
$query->close();
self::$db->close();
return $output;
}
public static function obtenerEdad65oMas() {
$query = self::$db->prepare("SELECT COUNT(*) AS edad65oMas FROM candidato WHERE id_rango_edad = 7");
$query->execute();
$resultado = $query->get_result();
$output = "0";
if($resultado->num_rows > 0) {
while ($data = $resultado->fetch_assoc()) {
$output= $data['edad65oMas'];
}
}
$query->close();
self::$db->close();
return $output;
}
public static function obtenerEstados() {
try {
$query = self::$db->prepare("SELECT estados.nombre AS estado, COUNT(*) AS cantidad
FROM estados
INNER JOIN info_candidatos ON info_candidatos.id_estado = estados.id
GROUP BY estados.nombre
ORDER BY estados.nombre
");
$query->execute();
$resultado = $query->get_result();
$estados = [];
while ($data = $resultado->fetch_assoc()) {
$estados[] = $data;
}
$query->close();
error_log(json_encode($estados));
return $estados;
} catch (Exception $e) {
error_log("Error al obtener los estados: " . $e->getMessage());
return [];
}
}
public static function obtenerExamenes() {
try {
$query = self::$db->prepare("SELECT nombre_examen AS examen , COUNT(*) AS cantidad
FROM examen
INNER JOIN candidato ON candidato.id_examen = examen.id_examen
GROUP BY nombre_examen
ORDER BY nombre_examen;
");
$query->execute();
$resultado = $query->get_result();
$examenes = [];
while ($data = $resultado->fetch_assoc()) {
$examenes[] = $data;
}
$query->close();
error_log(json_encode($examenes));
return $examenes;
} catch (Exception $e) {
error_log("Error al obtener los examenes: " . $e->getMessage());
return [];
}
}
public static function obtenerFecha($fechaInicio, $fechaFin) {
try {
$query = self::$db->prepare("SELECT COUNT(*) AS total
FROM candidato
WHERE DATE(fecha_entrada) = ?
AND DATE(fecha_salida) = ?;
");
$query->bind_param("ss", $fechaInicio, $fechaFin);
$query->execute();
$resultado = $query->get_result();
$fechas = [];
while ($data = $resultado->fetch_assoc()) {
$fechas[] = $data;
}
error_log("Resultados de la consulta: " . json_encode($fechas)); // <-- Agrega este log
$query->close();
return $fechas;
} catch (Exception $e) {
error_log("Error al obtener las fechas: " . $e->getMessage());
return [];
}
}
}
?>

172
models/UsuarioModel.php Normal file
View File

@ -0,0 +1,172 @@
<?php
require_once __DIR__ . '/../config/Database.php';
class UsuarioModel {
private $conn;
public function __construct() {
$this->conn = Database::getInstance();
}
/**
* Registrar un nuevo usuario
* @param string $usuario Nombre de usuario
* @param string $contrasena Contraseña del usuario
* @return array
*/
public function crearUsuario($usuario, $contrasena){
// Hashear contraseña
$contrasena_hash = password_hash($contrasena, PASSWORD_DEFAULT);
$stmt = $this->conn->prepare("INSERT INTO usuario (usuario, contrasena) VALUES (?, ?)");
$stmt->bind_param("ss", $usuario, $contrasena_hash);
if (!$stmt->execute()) {
return [
'estado' => 'error',
'mensaje' => 'Error al registrar usuario: ' . $stmt->error
];
}
$stmt->close();
return [
'estado' => 'exitoso',
'mensaje' => 'Usuario ' . $usuario . ' registrado exitosamente.'
];
}
public function actualizarUsuario($id, $usuario, $contrasena){
// Hashear contraseña
$contrasena_hash = password_hash($contrasena, PASSWORD_DEFAULT);
$stmt = $this->conn->prepare("UPDATE usuario SET usuario = ?, contrasena = ? WHERE id = ?");
$stmt->bind_param("ssi", $usuario, $contrasena_hash, $id);
if (!$stmt->execute()) {
return [
'estado' => 'error',
'mensaje' => 'Error al actualizar usuario: ' . $stmt->error
];
}
$stmt->close();
return [
'estado' => 'exitoso',
'mensaje' => 'Usuario ' . $usuario .' actualizado exitosamente.'
];
}
/**
* Iniciar sesión de un usuario
* @param string $usuario Nombre de usuario
* @param string $contrasena Contraseña del usuario
* @return array
*/
public function iniciarSesion($usuario, $contrasena) {
$stmt = $this->conn->prepare("SELECT contrasena FROM usuario WHERE usuario = ?");
$stmt->bind_param("s", $usuario);
if (!$stmt->execute()) {
return [
'estado' => 'error',
'mensaje' => 'Error al iniciar sesión, intentelo más tarde.',
'res' => $stmt->error
];
}
$stmt->store_result();
if ($stmt->num_rows == 0) {
return [
'estado' => 'error',
'mensaje' => 'Usuario o contraseña incorrectos.'
];
}
$stmt->bind_result($contrasena_hash);
$stmt->fetch();
if (password_verify($contrasena, $contrasena_hash)){
return [
'estado' => 'exitoso',
'mensaje' => 'Inicio de sesión exitoso.'
];
} else {
return [
'estado' => 'error',
'mensaje' => 'Usuario o contraseña incorrectos.'
];
}
}
public function obtenerUsuarios() {
// Preparar la consulta mysql usando msqli
$stmt = $this->conn->prepare("SELECT id, usuario FROM usuario");
if (!$stmt->execute()) {
return [
'estado' => 'error',
'mensaje' => 'Error al obtener usuarios: ' . $stmt->error
];
}
// Obtener el resultado
$result = $stmt->get_result();
// Si no hay resultados, retornar un array con estado y mensaje
if ($result->num_rows == 0) {
return [
'estado' => 'error',
'mensaje' => 'No hay usuarios registrados.'
];
}
return $result->fetch_all(MYSQLI_ASSOC);
}
public function buscarUsuario($usuario) {
$stmt = $this->conn->prepare("SELECT id, usuario FROM usuario WHERE usuario = ?");
$stmt->bind_param("s", $usuario);
if (!$stmt->execute()) {
return [
'estado' => 'error',
'mensaje' => 'Error al verificar usuario: ' . $stmt->error
];
}
$stmt->store_result();
// Si no hay resultados, retornar un array con estado y mensaje
if ($stmt->num_rows == 0) {
return [
'estado' => 'exitoso',
'mensaje' => 'No existe el usuario.'
];
} else {
$stmt->bind_result($id, $usuario);
$stmt->fetch();
return [
'estado' => 'exitoso',
'mensaje' => 'Usuario existe.',
'id' => $id,
'usuario' => $usuario
];
}
}
public function eliminarUsuario($id){
$stmt = $this->conn->prepare("DELETE FROM usuario WHERE id = ?");
$stmt->bind_param("i", $id);
if (!$stmt->execute()) {
return [
'estado' => 'error',
'mensaje' => 'Error al eliminar usuario: ' . $stmt->error
];
}
$stmt->close();
return [
'estado' => 'exitoso',
'mensaje' => 'Usuario eliminado exitosamente.'
];
}
}
?>

20
pruebaSesion.php Normal file
View File

@ -0,0 +1,20 @@
<?php
session_start();
if (!isset($_SESSION['usuario'])) {
header('Location: index.html');
exit();
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pagina protegida</title>
</head>
<body>
<h1>Test</h1>
</body>
</html>

7
test.php Normal file
View File

@ -0,0 +1,7 @@
<?php
//require_once __DIR__ . "/controllers/usuarioController.php";
// UsuarioController::registrarUsuario("root", "root");
?>

65
views/Main limpio.html Normal file
View File

@ -0,0 +1,65 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href='https://unpkg.com/boxicons@2.0.9/css/boxicons.min.css' rel='stylesheet'>
<link rel="stylesheet" href="../css/inicio.css">
<title>Gestión de usuarios</title>
</head>
<body>
<!-- SIDEBAR -->
<section id="sidebar">
<a href="inicio.html" class="brand"><i class='bx bx-code-alt icon' ></i> LANIA</a>
<ul class="side-menu">
<li><a href="inicio.html"><i class='bx bxs-dashboard icon' ></i>Dashboard</a></li>
<li><a href="formulario-candidato.html" target="_blank"><i class='bx bxs-dashboard icon' ></i>Formulario de registro</a></li>
<li><a href="control-candidatos.php"><i class='bx bxs-dashboard icon' ></i>Control candidatos</a></li>
<li><a href="control-candidatos.php" class="active"><i class='bx bxs-dashboard icon' ></i>Gestión usuarios</a></li>
<!--
<li class="divider" data-text="main">Main</li>
<li>
<a href="#"><i class='bx bxs-inbox icon' ></i> Elements <i class='bx bx-chevron-right icon-right' ></i></a>
<ul class="side-dropdown">
<li><a href="#">Alert</a></li>
<li><a href="#">Badges</a></li>
<li><a href="#">Breadcrumbs</a></li>
<li><a href="#">Button</a></li>
</ul>
</li>
<li><a href="#"><i class='bx bxs-chart icon' ></i> Charts</a></li>
<li><a href="#"><i class='bx bxs-widget icon' ></i> Widgets</a></li>
<li class="divider" data-text="tablas y formularios">Tablas y formularios</li>
<li><a href="#"><i class='bx bx-table icon' ></i> Tablas</a></li>
<li>
<a href="#"><i class='bx bxs-notepad icon' ></i> Formularios <i class='bx bx-chevron-right icon-right' ></i></a>
<ul class="side-dropdown">
<li><a href="formulario-candidato.html">Registro de candidato</a></li>
<li><a href="formulario-datos-candidato.php">Datos de candidato</a></li>
</ul>
</li>
-->
</ul>
</section>
<!-- .SIDEBAR -->
<section id="content">
<!-- ========== MAIN ========== -->
<main>
<h1 class="title" style="margin: 2% 1%">Control</h1>
</main>
<!-- .......... MAIN .......... -->
</section>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="../js/sidebar-navbar.js"></script>
<script src="https://website-widgets.pages.dev/dist/sienna.min.js" defer></script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB