3.8 Mejoras y adaptaciones.

En este apartado vamos a tratar de realizar algunos cambios en el código para mejorar la calidad del mismo. y mejorar la experiencia del usuario.

  • factorizaremos el código PHP para la conexión a la base de datos, creando un archivo común que se pueda incluir en todos los scripts que lo necesiten.
  • Añadiremos filtros a la tabla de empleados para que el usuario pueda buscar por nombre o departamento.
  • Añadiremos un botón para limpiar los filtros.
  • Añadiremos opciones para poder ordenar la tabla por alguna de sus coluamnas.
  • Añadiremos finalmente paginación para que la experiencia del usuario sea más fluida y no tenga que cargar todos los empleados de golpe.

Mejora 1: Factorizar PHP - Crear un archivo común bd.php

Objetivo:

Crear un archivo común bd.php que contenga una función para la conexión a la base de datos. De esta manera, evitamos repetir el código de conexión en cada script y mejoramos la mantenibilidad del proyecto.

También nos va a permitir poder concentrar en un lugar los posibles errores que puedan ocurrir en la conexión a la base de datos. Si esto ocurre, redirigiremos al usuario a una página de error y registraremos el error en un log del sistema.

Como este archivo lo pueden utilizar varios scripts, es importante que la conexión a la base de datos sea lo más genérica posible. Por eso, utilizaremos variables de entorno para almacenar las credenciales de la base de datos. De esta manera, no tendremos que modificar el código si cambiamos de servidor o de base de datos. Además lo guardaremos en una carpeta lib porque será accesible desde cualquier parte del proyecto.

Pasos:

1. Crear el archivo ./lib/bd.php:

Este archivo se encargará de proporcionar la conexión a la base de datos a cualquier script que lo incluya.

Crear una página de error:

Vamos a crear una página de error que se mostrará al usuario si hay un problema con la conexión a la base de datos. Esta página mostrará un mensaje amigable y un botón para volver a la página principal. La página de error se llamará error.html y contendrá un mensaje de error y un botón para volver a la página principal.

Página de error de sistema

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Error del sistema</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f8d7da;
            color: #721c24;
            text-align: center;
            padding: 50px;
        }
        h1 {
            font-size: 2em;
        }
        p {
            font-size: 1.2em;
        }
        .btn {
            background-color: #007bff;
            color: white;
            padding: 10px 20px;
            text-decoration: none;
            border-radius: 5px;
            font-size: 1em;
        }
        .btn:hover {
            background-color: #0056b3;
        }
    </style>
</head>
<body>
    <h1>¡Ups! Algo salió mal.</h1>
    <p>Actualmente estamos teniendo problemas técnicos. Por favor, espere un momento y vuelva a intentar.</p>
    <a href="index.php" class="btn">Volver a la página principal</a>
</body>
</html>

Contenido del archivo bd.php:

Código de conexión a la base de datos

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
function obtenerConexion() {
     // Conexión a la base de datos con PDO
    $host = getenv('MYSQL_HOST');
    $user = getenv('MYSQL_USER');
    $pass = getenv('MYSQL_PASSWORD');
    $base_de_datos = getenv('MYSQL_DB');

    // Crear la conexión
    $conexion = new mysqli($host, $user, $pass, $base_de_datos);

    // Comprobar si hay errores en la conexión
    if ($conexion->connect_error) {
        // Registrar el error en el log del sistema
        error_log("Error de conexión a la base de datos: " . $conexion->connect_error, 3, "logs/error.log");
        // Redirigir al usuario a la página de error
        header("Location: ../error.html");
        exit;
    }
    return $conexion; // Retornamos la conexión si todo está bien
}
?>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
function obtenerConexion() {
    $host = getenv('MYSQL_HOST');
    $user = getenv('MYSQL_USER');
    $pass = getenv('MYSQL_PASSWORD');
    $base_de_datos = getenv('MYSQL_DB');

    try {
        // Crear la conexión con PDO
        $conexion = new PDO("mysql:host=$host;dbname=$base_de_datos", $user, $pass);

        // Configurar el modo de error para que lance excepciones
        $conexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

        return $conexion; // Retornamos la conexión si todo está bien
    } catch (PDOException $e) {
        // Registrar el error en el log
        error_log("Error de conexión a la base de datos: " . $e->getMessage(), 3, "logs/error.log");

        // Redirigir al usuario a la página de error
        header("Location: ,,/error.html");
        exit; // Detener la ejecución del script
    }
}
?>

Explicación:

  • obtenerConexion(): Esta función devuelve una conexión a la base de datos. Si hay un error en la conexión, devuelve false para que el script principal lo maneje.
  • new mysqli(): Usamos MySQLi orientado a objetos para realizar la conexión.
  • new PDO(): Usamos PDO para realizar la conexión.
  • Validación de conexión: Si la conexión falla, el script no se detiene abruptamente, sino que el error se maneja en el script principal.
  • error_log(): Esta función registrará el error en un archivo llamado error.log. Este archivo se debe crear en una carpeta llamada logs. Si no quieres que los usuarios vean el error, puedes configurar los permisos de este archivo de log para que sea solo accesible por el administrador del sistema.
  • header("Location: error.html");: Si la conexión falla, el script redirige al usuario a error.html. La página de error mostrará el mensaje amigable y evitará que el usuario vea detalles técnicos del error.
  • exit;: Después de la redirección, terminamos el script para que no continúe ejecutándose.

HTML en caso de error

Y en el fichero de logs/error.log se registrará el error de conexión.

Error de conexión a la base de datos: SQLSTATE[HY000] [2002] Connection refused

Crear la carpeta logs y el archivo error.log:

Crear el archivo de log (logs/error.log):

Debemos crear una carpeta logs en el directorio raíz de tu aplicación (o en un lugar adecuado) para almacenar los logs. Asegúrate de que el archivo error.log tenga los permisos correctos para que el servidor web (por ejemplo, Apache o Nginx) pueda escribir en él.

  1. Crea la carpeta logs/.

  2. Crea el archivo logs/error.log.

  3. Asegúrate de que el archivo tenga permisos adecuados:

    • chmod 777 logs/error.log (si el servidor necesita permisos de escritura).

Ahora, cuando haya un error de conexión, el error será registrado en el archivo error.log.

2. Incluir bd.php en los scripts PHP:

Una vez que hemos creado bd.php, podemos incluirlo en todos los scripts PHP que necesiten conectarse a la base de datos. De esta manera, evitamos repetir el código de conexión en cada archivo. Además podemos controlar mejor los errores de conexión.

Ejemplo de cómo usar bd.php en otro script:

Ejemplo de uso de bd.php

<?php
// Incluir el archivo de conexión
include '../lib/bd.php';

// Obtener la conexión a la base de datos
$conexion = obtenerConexion();

// Verificar si la conexión fue exitosa
if (!$conexion) {
    // Si la conexión falló, devolver un error en formato JSON
    echo json_encode(['success' => false, 'error' => 'No se pudo conectar a la base de datos.']);
    exit; // Terminar la ejecución del script
}

// Realizar operaciones con la base de datos
$query = "SELECT * FROM employees";
$resultado = $conexion->query($query);

// Procesar los resultados
while ($fila = $resultado->fetch_assoc()) {
    echo "ID: " . $fila['emp_no'] . " - Nombre: " . $fila['first_name'] . "<br>";
}

// Cerrar la conexión
$conexion->close();
?>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
// Incluir el archivo de conexión
include '../lib/bd.php';

// Obtener la conexión a la base de datos
$conexion = obtenerConexion();

// Verificar si la conexión fue exitosa
if (!$conexion) {
    echo json_encode(['success' => false, 'error' => 'No se pudo conectar a la base de datos.']);
    exit;
}

// Realizar operaciones con la base de datos
$query = "SELECT * FROM employees";
$resultado = $conexion->query($query);

// Procesar los resultados
while ($fila = $resultado->fetch(PDO::FETCH_ASSOC)) {
    echo "ID: " . $fila['emp_no'] . " - Nombre: " . $fila['first_name'] . "<br>";
}

// Cerrar la conexión
$conexion = null;
?>

3. Modificar todos los scripts PHP existentes:

Para utilizar la función obtenerConexion(), en lugar de definir la conexión en cada script, simplemente incluimos bd.php y usamos la función.

  • Incluye bd.php al principio de cada script PHP que necesite conectarse a la base de datos.
  • Sustituye las líneas de conexión en cada script con la llamada a obtenerConexion().

Realiza los cambios necesarios en los ficheros: - employees.php - eliminarEmpleado.php - agregarEmpleado.php - getEmpleados.php

4. Beneficios:

  • Modularidad: El código de conexión es centralizado y reutilizable, lo que hace que el código sea más limpio y mantenible.
  • Mantenimiento: Si en el futuro necesitamos cambiar las credenciales o parámetros de conexión, solo lo haremos en bd.php y no en todos los scripts.
  • Mejor control de errores: Al devolver false en lugar de usar die(), el script principal puede manejar el error de una manera más controlada, como devolver un mensaje de error en formato JSON.

Conclusión:

Esta es una mejora sencilla pero muy útil que facilita el mantenimiento del código a largo plazo y mejora la organización del proyecto. Ahora, en lugar de tener código duplicado para la conexión en cada script, todo está centralizado en bd.php.

En caso de error el usuario verá un mensaje amigable y el error será registrado en un log del sistema. Esto es especialmente útil para la depuración y el mantenimiento del sistema.


Mejora 2: Barra de herramientas

La barra de herramientas tendrá los siguientes elementos:

  1. Botón para añadir un empleado: Un botón que abre el formulario para crear un nuevo empleado.
  2. Campo de búsqueda: Un input para filtrar empleados por nombre o apellido.
  3. Filtro de estado: Un select para filtrar los empleados por estado (todos, activos, inactivos).
  4. Filtro de departamento: Un select que permitirá elegir un departamento específico (o mostrar todos los empleados si selecciona "Todos").

Pasos para implementar la barra de herramientas:

1. Crear la barra de herramientas en el archivo employees.php (HTML)

Vamos a agregar la barra de herramientas justo encima de la tabla de empleados.

Código HTML para la barra de herramientas

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<!-- Barra de herramientas -->
<div class="toolbar">
    <button id="btnAgregarEmpleado">Añadir Empleado</button>
    <input type="text" id="buscarEmpleado" placeholder="Buscar por nombre o apellido">
     <select id="filtroDepartamento">
        <option value="todos">Todos los Departamentos</option>
        <!-- Los departamentos serán cargados dinámicamente mediante PHP o AJAX -->
    </select>
    <select id="filtroEstado">
        <option value="todos">Todos</option>
        <option value="activos">Activos</option>
        <option value="inactivos">Inactivos</option>
    </select>
</div>

En la barra de herramientas hemos añadido:

  • Añadir Empleado: Un botón que abrirá el formulario para agregar un nuevo empleado.
  • Buscar: Un campo de texto que permitirá buscar empleados por su nombre o apellido.
  • Departamento: Un select que permitirá filtrar los empleados por su departamento. Este select se llenará dinámicamente con los departamentos de la base de datos.
  • Estado: Un select que permitirá filtrar los empleados por su estado (activos o inactivos).

2. Estilos CSS para la barra de herramientas:

Puedes agregar estilos básicos para la barra de herramientas para que se vea bien y esté alineada correctamente.

Código CSS para la barra de herramientas

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* Estilos generales */
body {
    font-family: Arial, sans-serif;
    margin: 0;
    padding: 20px;
    background-color: #f4f4f4;
}

/* Estilos para la barra de herramientas */
.toolbar {
    margin-bottom: 20px;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.toolbar button,
.toolbar input,
.toolbar select {
    padding: 10px;
    margin: 5px;
    font-size: 1rem;
}

3. Obtener los departamentos y añadir los filtros de estado y departamento (PHP y AJAX):

a. Crear un select de departamentos:

En el archivo employees.php, vamos a agregar un bloque de PHP que obtenga los departamentos de la base de datos y los agregue al <select> de departamentos.

Código PHP para obtener los departamentos

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    <?php
    // Conectar a la base de datos (usando la función que ya tenemos en bd.php)
    include 'bd.php';
    $conexion = obtenerConexion();

    // Obtener los departamentos
    $query = "SELECT dept_no, dept_name FROM departments";
    $resultado = $conexion->query($query);

    // Llenar el select de departamentos
    $departamentos = [];
    while ($row = $resultado->fetch_assoc()) {
        $departamentos[] = $row;
    }

    $conexion->close();
    ?>

    <script>
    // Cargar los departamentos en el select
    const selectDepartamento = document.getElementById('filtroDepartamento');
    const departamentos = <?php echo json_encode($departamentos); ?>;

    departamentos.forEach(departamento => {
        const option = document.createElement('option');
        option.value = departamento.dept_no;
        option.textContent = departamento.dept_name;
        selectDepartamento.appendChild(option);
    });
    </script>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    <?php
    // Conectar a la base de datos (usando la función que ya tenemos en bd.php)
    include 'bd.php';
    $conexion = obtenerConexion();

    // Obtener los departamentos
    $query = "SELECT dept_no, dept_name FROM departments";
    $stmt = $conexion->prepare($query);
    $stmt->execute();
    $departamentos = $stmt->fetchAll(PDO::FETCH_ASSOC);

    $conexion = null;
    ?>

    <script>
    // Cargar los departamentos en el select
    const selectDepartamento = document.getElementById('filtroDepartamento');
    const departamentos = <?php echo json_encode($departamentos); ?>;

    departamentos.forEach(departamento => {
        const option = document.createElement('option');
        option.value = departamento.dept_no;
        option.textContent = departamento.dept_name;
        selectDepartamento.appendChild(option);
    });
    </script>

4. Filtrar empleados con AJAX:

Cada vez que el usuario cambie el valor de cualquiera de los filtros (búsqueda, estado o departamento), haremos una consulta AJAX para obtener los empleados que coincidan con los filtros seleccionados. Modificamos la consulta de la base de datos según los valores de los filtros.

Código JavaScript para filtrar empleados

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// Función para realizar la consulta de empleados con filtros
function obtenerEmpleadosConFiltros() {
    const buscar = document.getElementById('buscarEmpleado').value;
    const estado = document.getElementById('filtroEstado').value;
    const departamento = document.getElementById('filtroDepartamento').value;

    // Crear el objeto con los filtros
    const filtros = {
        buscar: buscar,
        estado: estado,
        departamento: departamento
    };

    // Realizar la solicitud AJAX
    fetch('obtenerEmpleados.php', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(filtros)
    })
    .then(response => response.json())
    .then(data => {
        // Limpiar la tabla antes de agregar los nuevos empleados
        const tablaCuerpo = document.getElementById('empleados').getElementsByTagName('tbody')[0];
        tablaCuerpo.innerHTML = '';

        // Agregar los empleados a la tabla
        data.empleados.forEach(empleado => {
            const row = tablaCuerpo.insertRow();
            row.innerHTML = `
                <td>${empleado.emp_no}</td>
                <td>${empleado.first_name}</td>
                <td>${empleado.last_name}</td>
                <td>${empleado.birth_date}</td>
                <td>${empleado.hire_date}</td>
                <td>${empleado.gender}</td>
                <td><button onclick="editarEmpleado(${empleado.emp_no})">Editar</button></td>
                <td><button onclick="eliminarEmpleado(${empleado.emp_no})">Eliminar</button></td>
            `;
        });
    });
}

// Eventos para los filtros
document.getElementById('buscarEmpleado').addEventListener('input', obtenerEmpleadosConFiltros);
document.getElementById('filtroEstado').addEventListener('change', obtenerEmpleadosConFiltros);
document.getElementById('filtroDepartamento').addEventListener('change', obtenerEmpleadosConFiltros);

5. Modificar obtenerEmpleados.php para recibir los filtros:

Ahora, creamos un archivo obtenerEmpleados.php que recibirá los filtros desde el frontend y devolverá los empleados filtrados.

Código PHP para obtener empleados filtrados

<?php
// Incluir el archivo de conexión
include '../lib/bd.php';

// Obtener los filtros desde el frontend
$filtros = json_decode(file_get_contents('php://input'), true);

// Construir la consulta con filtros
$query = "SELECT * FROM employees WHERE 1";

// Filtrar por búsqueda
if (!empty($filtros['buscar'])) {
    $query .= " AND (first_name LIKE '%" . $filtros['buscar'] . "%' OR last_name LIKE '%" . $filtros['buscar'] . "%')";
}

// Filtrar por estado
if ($filtros['estado'] != 'todos') {
    $estado = $filtros['estado'] == 'activos' ? '9999-01-01' : '< NOW()'; // Aquí estamos asumiendo que 'activos' significa con fecha '9999-01-01'
    $query .= " AND hire_date <= '$estado'"; // Cambia esto según cómo gestiones los estados en tu base de datos
}

// Filtrar por departamento
if ($filtros['departamento'] != 'todos') {
    $query .= " AND emp_no IN (SELECT emp_no FROM dept_emp WHERE dept_no = '" . $filtros['departamento'] . "')";
}

// Conectar y ejecutar la consulta
$conexion = obtenerConexion();
$resultado = $conexion->query($query);

// Obtener los resultados
$empleados = [];
while ($row = $resultado->fetch_assoc()) {
    $empleados[] = $row;
}

echo json_encode(['empleados' => $empleados]);

$conexion->close();
?>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
// Incluir el archivo de conexión
include 'bd.php';
// Obtener los filtros desde el frontend
$filtros = json_decode(file_get_contents('php://input'), true);
// Construir la consulta con filtros
$query = "SELECT * FROM employees WHERE 1";
// Filtrar por búsqueda
if (!empty($filtros['buscar'])) {
    $query .= " AND (first_name LIKE :buscar OR last_name LIKE :buscar)";
}
// Filtrar por estado
if ($filtros['estado'] != 'todos') {
    $estado = $filtros['estado'] == 'activos' 
        ? ' AND EXISTS (SELECT * FROM dept_emp WHERE dept_emp.emp_no = employees.emp_no AND dept_emp.to_date = "9999-01-01")'
        : ' AND NOT EXISTS (SELECT * FROM dept_emp WHERE dept_emp.emp_no = employees.emp_no AND dept_emp.to_date = "9999-01-01")'; 
        // Aquí estamos asumiendo que 'activos' significa con fecha '9999-01-01' 
    $query .= $estado; // Cambia esto según cómo gestiones los estados en tu base de datos
}
// Filtrar por departamento
if ($filtros['departamento'] != 'todos') {
    $query .= " AND emp_no IN (SELECT emp_no FROM dept_emp WHERE dept_no = :departamento)";
}
// Conectar y ejecutar la consulta
$conexion = obtenerConexion();
$stmt = $conexion->prepare($query);
// Vincular los parámetros
if (!empty($filtros['buscar'])) {
    $buscar = '%' . $filtros['buscar'] . '%';
    $stmt->bindParam(':buscar', $buscar, PDO::PARAM_STR);
}
if ($filtros['departamento'] != 'todos') {
    $stmt->bindParam(':departamento', $filtros['departamento'], PDO::PARAM_STR);
}
$stmt->execute();
// Obtener los resultados
$empleados = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['empleados' => $empleados]);
$conexion = null;
?>

Modificar la página employees.php para incluir el nuevo script de obtenerEmpleados.php.

Ahora tenemos el mismo código en dos sitios distintos, la carga de empleados en la tabla la tenemos en el script employees.php y también en el script obtenerEmpleados.php. Vamos a modificar el script employees.php para que cargue los empleados desde el script obtenerEmpleados.php y no desde la base de datos directamente.

Si queremos tener un código limpio y ordenado, es mejor que la carga de empleados la hagamos desde el script obtenerEmpleados.php y no desde el script employees.php.

Primero vamos a modificar employees.php para que no cargue los empleados directamente desde la base de datos, sino que lo haga desde el script obtenerEmpleados.php. Localizamos en la tabla el código phpde carga de empleados y lo eliminamos, dejando solo el tbody de la tabla.

Modificación en empleados.php

1
2
3
<tbody>
    <!-- Los empleados se cargarán aquí con AJAX -->
</tbody>

Ahora necesitamos cargar los empleados utilizando AJAX. Para ello, vamos a modificar el script employees.php para que cargue los empleados desde el script obtenerEmpleados.php al cargar la página. Añadimos el siguiente código al final del archivo employees.js:

Código JavaScript para cargar empleados

1
2
3
4
// Cargar los empleados al cargar la página
window.onload = function() {
    obtenerEmpleadosConFiltros();
};

De esta manera, al cargar la página, se llamará a la función obtenerEmpleadosConFiltros() que realizará una consulta AJAX al script obtenerEmpleados.php y cargará los empleados en la tabla.

Ahora nos queda modificar el script obtenerEmpleados.php para que cargue los empleados desde la base de datos. En este caso debemos conseguir que funcione en la carga inicial en la que no le pasamos ningún filtro.


Resumen de la mejora:

  1. Barra de herramientas: Agregamos un botón para agregar un empleado, un campo de búsqueda, un filtro de estado (activos/inactivos) y un filtro de departamento.
  2. Filtros: Utilizamos AJAX para obtener los empleados filtrados según los valores seleccionados por el usuario.
  3. Resultados dinámicos: La tabla de empleados se actualiza sin necesidad de recargar la página.
  4. Carga inicial: Al cargar la página, se cargan todos los empleados por defecto.

La siguiente mejora es la implementación de la ordenación en la tabla.

Mejora 3: Ordenación de la tabla por columnas (ascendente/descendente)

Este paso permitirá que los usuarios puedan ordenar los empleados en la tabla de acuerdo con las columnas, como ID, nombre, apellido, fecha de nacimiento, fecha de contratación, etc.

Objetivo:

  • Implementar un sistema de ordenación ascendente y descendente al hacer clic en las cabeceras de las columnas.
  • Agregar botones de ordenación (+/-) en la cabecera de cada columna.
  • Ordenar los datos sin recargar la página usando AJAX.

Pasos para implementar la ordenación:

  1. Añadir botones de ordenación en la cabecera de la tabla: Se añadirán botones + y - en cada columna, permitiendo que el usuario seleccione si quiere ordenar los datos de manera ascendente o descendente.

  2. Modificar la consulta en obtenerEmpleados.php para permitir la ordenación dinámica según la columna seleccionada.

  3. Envío de parámetros de ordenación con AJAX: Al hacer clic en un botón de ordenación, se enviarán los parámetros de la columna y el orden al servidor mediante AJAX.

  4. Actualizar la tabla sin recargar la página: La tabla se actualizará con los datos ordenados, sin necesidad de recargar la página.


1. Modificar el HTML de la tabla para agregar los botones de ordenación

Ahora tenemos una función obtenerEmpleadosConFiltros() que carga los empleados en la tabla, pero que no tiene en cuenta posibles ordenaciones. Vamos a modificar primero esta función para que tenga en cuenta la ordenación de los empleados. Y luego modificaremos el HTML de la tabla para añadir los botones de ordenación.

Código JavaScript para ordenar empleados

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// creamos las variables globales para recordar que columna y orden hemos seleccionado
let ordenColumna = "emp_no";
let ordenTipo = "asc";
// Función para ordenar la tabla por una columna específica
function obtenerEmpleadosConFiltros(pagina = 1, columna = null, orden = null) {
    ...
    if (columna) {
        ordenColumna = columna;
    }
    if (orden) {
        ordenTipo = orden;
    }

    // Crear el objeto con los filtros
    const filtros = {
        buscar: buscar,
        estado: estado,
        departamento: departamento,
        ordenarPor: columna,  // Columna por la que se va a ordenar
        orden: orden         // Orden (ascendente o descendente)
    };
    ...
}
Hemos añadido la información del orden al json que enviamos al servidor. Ahora vamos a modificar el HTML de la tabla para añadir los botones de ordenación.

En la cabecera de la tabla, añadimos los botones + y - para que el usuario pueda seleccionar cómo quiere ordenar las columnas.

Código HTML para la tabla de empleados

html lineenums="1" <!-- Tabla de empleados --> <table id="empleadosTable"> <thead> <tr> <th> <span>ID</span> <button onclick="obtenerEmpleadosConFiltros('emp_no', 'asc')">+</button> <button onclick="obtenerEmpleadosConFiltros('emp_no', 'desc')">-</button> </th> <th> <span>Nombre</span> <button onclick="obtenerEmpleadosConFiltros('first_name', 'asc')">+</button> <button onclick="obtenerEmpleadosConFiltros('first_name', 'desc')">-</button> </th> <th> <span>Apellido</span> <button onclick="obtenerEmpleadosConFiltros('last_name', 'asc')">+</button> <button onclick="obtenerEmpleadosConFiltros('last_name', 'desc')">-</button> </th> <th> <span>Fecha de Nacimiento</span> <button onclick="obtenerEmpleadosConFiltros('birth_date', 'asc')">+</button> <button onclick="obtenerEmpleadosConFiltros('birth_date', 'desc')">-</button> </th> <th> <span>Fecha de Contratación</span> <button onclick="obtenerEmpleadosConFiltros('hire_date', 'asc')">+</button> <button onclick="obtenerEmpleadosConFiltros('hire_date', 'desc')">-</button> </th> <th> <span>Género</span> <button onclick="obtenerEmpleadosConFiltros('gender', 'asc')">+</button> <button onclick="obtenerEmpleadosConFiltros('gender', 'desc')">-</button> </th> <th>Acciones</th> </tr> </thead> <tbody> <!-- Los empleados se cargarán aquí con AJAX --> </tbody> </table>

Explicación:

  • Añadimos los botones + y - para cada columna. Al hacer clic en estos botones, se llamará a la función ordenar() con el nombre de la columna y el orden (ascendente o descendente).

Explicación:

  • filtros.ordenarPor: Se agrega el nombre de la columna por la que se va a ordenar (por ejemplo, emp_no, first_name, etc.).
  • filtros.orden: Se agrega el orden (ascendente o descendente).
  • AJAX: Se envían los filtros y los parámetros de ordenación al servidor.

2. Modificar obtenerEmpleados.php para manejar la ordenación

En obtenerEmpleados.php, vamos a agregar lógica para ordenar los empleados según los parámetros recibidos desde el frontend.

Código PHP para ordenar los empleados:

Añadir este código deespués de la sección de filtros en el archivo obtenerEmpleados.php.

Código PHP para ordenar los empleados

1
2
3
4
5
6
7
8
9
<?php
...
// Ordenar por la columna seleccionada
if (!empty($filtros['ordenarPor'])) {
    $query .= " ORDER BY " . $filtros['ordenarPor'] . " " . strtoupper($filtros['orden']);
} else {
        $query .= " ORDER BY emp_no ASC"; // Ordenar por defecto por emp_no
    }
...

Explicación:

  1. Añadir la cláusula ORDER BY a la consulta:
  2. Si se recibe el parámetro ordenarPor (columna por la que ordenar) y orden (ascendente o descendente), la consulta se modificará para incluir ORDER BY.

  3. Parámetros de ordenación:

  4. $filtros['ordenarPor']: Es el nombre de la columna por la que ordenar (por ejemplo, emp_no, first_name, etc.).
  5. $filtros['orden']: Es el tipo de orden (ascendente o descendente). Se usa strtoupper() para asegurar que el valor sea en mayúsculas, ya que MySQL usa ASC y DESC.

4. Resumen de la mejora:

  1. Botones de ordenación: Añadimos botones + y - en cada columna de la tabla para permitir ordenar los empleados por esa columna.
  2. AJAX para ordenar: Enviamos los parámetros de ordenación al backend mediante AJAX.
  3. Modificar consulta SQL: El servidor recibe los parámetros y modifica la consulta SQL para ordenar los resultados.
  4. Actualizar la tabla: Los empleados se actualizan dinámicamente en la tabla según la ordenación seleccionada, sin recargar la página.

Mejora 4: Mejoras visuales:

  1. Estilos de los formularios (Validaciones visuales):

    • Asegurarnos de que los formularios de alta de empleados y edición sean visualmente más claros y fáciles de usar.
    • Incluir retroalimentación visual cuando un campo no pasa la validación (mostrar un borde rojo, un mensaje de error, etc.).
    • Añadir íconos de validación (como un check verde para campos correctos y un icono de error para los incorrectos).
  2. Notificaciones visuales para el usuario:

    • Notificaciones de éxito y error en la parte superior de la pantalla.
    • Mensajes visuales amigables que le informen al usuario si la operación fue exitosa o si hubo un error (por ejemplo, “Empleado agregado exitosamente” o “Error al eliminar el empleado”).
  3. Mejoras en la tabla:

    • Resaltar la fila seleccionada al pasar el mouse o al hacer clic en ella.
    • Alternar colores de las filas (cada fila podría tener un color de fondo alternado para mejorar la legibilidad).
    • Cambiar el color del botón de ordenación cuando el usuario haga clic en él, indicando cuál columna está ordenada.
  4. Botones y formularios interactivos:

    • Botones de acción (Guardar, Eliminar, Cancelar) estilizados de manera clara y con transiciones suaves para indicar que están activos.
    • Botón de "Volver" con estilo destacado para permitir que el usuario regrese a la página principal desde los formularios o páginas de error.
  5. Diseño de la página de error:

    • La página error.html puede mejorar con un diseño más atractivo y amigable.
    • Un mensaje de error claro y un botón de volver que sea fácil de ver y usar.
  6. Carga dinámica de la tabla de empleados:

    • Agregar un indicador de carga (spinner o mensaje de "Cargando...") mientras se están trayendo los empleados mediante AJAX.
  7. Estilos para los filtros:

    • Añadir bordes y sombras sutiles en los filtros de búsqueda y selección para que los campos sean más atractivos.
    • Efectos hover para botones y filtros para indicar que son interactivos.

1. Estilos de los formularios (Validación de campos):

  1. Validación visual en los campos del formulario:
  2. Si el campo no es válido, cambiar el borde a rojo y mostrar un icono de error.
  3. Si el campo es válido, mostrar un check verde.

CSS para validación:

Añadimos estilos CSS para los campos de formulario.

  • Los campos válidos tendrán un borde verde y los inválidos un borde rojo.
  • Mostraremos un mensaje de error si el campo no es válido.

Código CSS para validación de formularios

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* Estilos para campos válidos e inválidos */
input:invalid, select:invalid {
    border-color: red;
}

input:valid, select:valid {
    border-color: green;
}

input:invalid + .error-message, select:invalid + .error-message {
    display: block;
    color: red;
}

/* Estilo para los iconos */
.error-icon {
    color: red;
    font-size: 1.2em;
}

.success-icon {
    color: green;
    font-size: 1.2em;
}
  1. HTML del formulario con iconos de validación:

Código HTML para el formulario de alta de empleado

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<!-- Formulario de alta de empleado -->
<form id="formAltaEmpleado">
    <label for="nombre">Nombre:</label>
    <input type="text" id="nombre" name="nombre" required>
    <span class="error-message">El nombre es obligatorio</span>
    <i class="error-icon">!</i>
    <label for="apellido">Apellido:</label>
    <input type="text" id="apellido" name="apellido" required>
    <span class="error-message">El apellido es obligatorio</span>
    <i class="error-icon">!</i>
    <!-- Más campos aquí -->
    <button type="button" onclick="guardarEmpleado()">Guardar</button>
    <button type="button" onclick="cerrarFormulario()">Cancelar</button>
</form>

2. Notificaciones visuales para el usuario:

Vamos a crear una notificación de éxito y error que aparecerá en la parte superior de la página después de una acción como agregar o eliminar un empleado.

Código CSS para las notificaciones

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/* Estilo general para las notificaciones */
.notification {
    position: fixed;
    top: 10px;
    left: 50%;
    transform: translateX(-50%);
    padding: 15px;
    background-color: #f44336; /* Rojo por defecto */
    color: white;
    border-radius: 5px;
    display: none;
    font-size: 1.2em;
}

.notification.success {
    background-color: #4CAF50; /* Verde */
}

.notification.error {
    background-color: #f44336; /* Rojo */
}

Código HTML para las notificaciones

1
2
3
4
5
6
7
8
<!-- Notificación de éxito -->
<div id="successNotification" class="notification success">
    ¡Empleado agregado exitosamente!
</div>
<!-- Notificación de error -->
<div id="errorNotification" class="notification error">
    Hubo un error al agregar el empleado.
</div>

Código JavaScript para mostrar notificaciones

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// Función para mostrar la notificación
function mostrarNotificacion(tipo, mensaje) {
    const notificacion = tipo === 'success' ? document.getElementById('successNotification') : document.getElementById('errorNotification');
    notificacion.innerHTML = mensaje;
    notificacion.style.display = 'block';

    // Ocultar la notificación después de 3 segundos
    setTimeout(() => {
        notificacion.style.display = 'none';
    }, 3000);
}

// Mostrar notificación de éxito
mostrarNotificacion('success', 'Empleado agregado exitosamente!');

// Mostrar notificación de error
mostrarNotificacion('error', 'Hubo un error al agregar el empleado.');

3. Mejoras visuales en la tabla:

  1. Resaltar la fila seleccionada al pasar el mouse o al hacer clic en ella:

Código CSS para alternar colores en las filas

1
2
3
4
5
6
7
8
9
/* Resaltar la fila al pasar el mouse */
tr:hover {
    background-color: #f1f1f1;
}

/* Resaltar la fila seleccionada */
tr.selected {
    background-color: #e0e0e0;
}
  1. Alternar colores en las filas para mejorar la legibilidad:

Código CSS para alternar colores en las filas

1
2
3
4
/* Alternar colores de las filas */
tr:nth-child(even) {
    background-color: #f9f9f9;
}
  1. Botones de ordenación visualmente claros:

Código CSS para botones de ordenación

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/* Botones de ordenación */
button {
    background-color: #007bff;
    color: white;
    padding: 5px 10px;
    font-size: 1rem;
    border: none;
    cursor: pointer;
}

button:hover {
    background-color: #0056b3;
}

4. Mejora de la página de error:

Mejoramos la página error.html con un diseño amigable y moderno.

Código HTML para la página de error

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Error del sistema</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f8d7da;
            color: #721c24;
            text-align: center;
            padding: 50px;
        }
        h1 {
            font-size: 2em;
        }
        p {
            font-size: 1.2em;
        }
        .btn {
            background-color: #007bff;
            color: white;
            padding: 10px 20px;
            text-decoration: none;
            border-radius: 5px;
            font-size: 1em;
        }
        .btn:hover {
            background-color: #0056b3;
        }
    </style>
</head>
<body>
    <h1>¡Ups! Algo salió mal.</h1>
    <p>Actualmente estamos teniendo problemas técnicos. Por favor, espere un momento y vuelva a intentar.</p>
    <a href="index.php" class="btn">Volver a la página principal</a>
</body>
</html>

Resumen de la mejora visual:

  1. Validaciones visuales: Añadimos retroalimentación visual para los campos del formulario, mostrando iconos de error o éxito.
  2. Notificaciones de éxito y error: Creamos un sistema de notificaciones que se muestran al usuario cuando la operación se realiza con éxito o falla.
  3. Mejoras en la tabla: Resaltamos las filas seleccionadas y alternamos colores en las filas para mejorar la legibilidad. Añadimos botones de ordenación visualmente claros.
  4. Página de error: Diseñamos una página de error amigable que muestra

Mejora 5: Implementación de la paginación:

El objetivo es mejorar el rendimiento y la usabilidad de la tabla de empleados cuando la cantidad de registros es grande. Vamos a implementar un sistema de paginación para que los empleados se carguen en páginas limitadas, y el usuario pueda navegar entre ellas.

Pasos para implementar la paginación:

  1. Refactorizar el código de obtenerEmpleados.php para que soporte la paginación.
  2. Modificar la consulta SQL para que devuelva un número limitado de registros por página.
  3. Calcular el número total de páginas y el número de empleados para mostrar el número correcto de páginas de navegación.
  4. Añadir los botones de navegación (páginas anteriores/siguientes y números de página).
  5. Usar AJAX para actualizar la tabla sin recargar la página.

1. Refactorizar el código de obtenerEmpleados.php:

El archivo obtenerEmpleados.php se encargará de recibir los parámetros de paginación y devolver los empleados correspondientes a la página solicitada. Pero este archivvo lo tenemos creado y hemo sido construyendo el código conforme hemos ampliado la funcionalidad. Al final ha quedado un código poco manejable y en el que sería difícil implementar la paginación. Por lo que vamos a crear un nuevo archivo llamado obtenerEmpleadosFuncs.php que contendrá todas las funciones necesarias para hacer el código más manejable además implementaremos las funcionalidades necesarias para la paginación.

Para ello necesitamos saber que la sentencia sql que utilicemos para sacar los empleados la tenemos que ejecutar dos veces, la primera para obtener información de los empleados y la segunda para contar el número de empleados que tenemos segón los filtros que hemos aplicado.

Necesitaremos saber de antemano cuantos registros tenemos para poder calcular el número de páginas que vamos a mostrar. Por lo que la consulta para contar los empleados la haremos sin aplicar el LIMIT y OFFSET y la consulta para obtener los empleados sí que la haremos con el LIMIT y OFFSET.

  • LIMIT: Especifica el número máximo de registros a devolver.
  • OFFSET: Especifica el número de registros a omitir antes de comenzar a devolver los resultados.

Para poder trabajar de manera más eficiente vamos a dividir la consulta en varias partes, primero una función que nos devuelva la consulta báscica de los empleados. Utilizaremos un parámetro para poder especificar si queremos contar los empleados o si queremos obtener los empleados. La función se llamará consutaSelect($count = false).

Código PHP para la consulta de empleados

1
2
3
4
5
<?php
function consultaSelect($count = false) {
    return $count ? "SELECT COUNT(*) FROM employees WHERE 1 " : "SELECT * FROM employees WHERE 1 ";
}
?>

Hemos añadido WHERE 1 para que sea más sencillo añadir los filtros ya que la clausula WHERE ya la tenemos. Esto es útil para poder añadir filtros después.

Vamos ahora con las funciones que nos van a permitir completar la consulta con los filtros que hemos añadido. Vamos a crear una función para cada filtro que tenemos en la tabla de empleados. Para ello hay que recordar que la petición a este escript nos lleara un json con los filtros que hemos añadido. Un ejemplo de este json sería el siguiente:

1
2
3
4
5
6
7
8
{
    "buscar": "nombre",
    "estado": "activos",
    "departamento": "todos",
    "ordenarPor": "emp_no",
    "orden": "asc",
    "pagina": 1
}

Vamos a crear una función para cada uno de los filtros que tenemos en la tabla de empleados. Estas funciones se encargarán de añadir los filtros a la consulta. Los filtros son buscar, estado y departamento. Vamos a crear una función para cada uno de ellos.

Código PHP para los filtros

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    <?php
    /**
     * Construye la consulta SQL para filtrar por nombre
     * @param array $filtros Filtros de búsqueda
     * @return string Consulta SQL
     */
    function filtroBuscar($filtros) {
        return " AND (first_name LIKE :buscar OR last_name LIKE :buscar)";
    }
    /**
     * Construye la consulta SQL para filtrar por estado
     * @param array $filtros Filtros de búsqueda
     * @return string Consulta SQL
     */
    function filtroEstado($filtros) {
    if ($filtros['estado'] != 'todos') {
                $estado = $filtros['estado'] == 'activos' 
                    ? ' AND EXISTS (SELECT * FROM dept_emp WHERE dept_emp.emp_no = employees.emp_no AND dept_emp.to_date = "9999-01-01")'
                    : ' AND NOT EXISTS (SELECT * FROM dept_emp WHERE dept_emp.emp_no = employees.emp_no AND dept_emp.to_date = "9999-01-01")'; 
                return $estado; 
            }
        return "";
    }
    /**
     * Construye la consulta SQL para filtrar por departamento
     * @param array $filtros Filtros de búsqueda
     * @return string Consulta SQL
     */
    function filtroDepartamento($filtros) {
        if ($filtros['departamento'] != 'todos') {
                return " AND emp_no IN (SELECT emp_no FROM dept_emp WHERE dept_no = :departamento)";
            }
        return "";
    }
    ?>

Ahora vamos a crear una función que añada la clausula ORDER BY a la consulta. Esta función se encargará de añadir la ordenación a la consulta. La función se llamará ordenarPos($filtros).

Código PHP para la ordenación

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    <?php
    /**
     * Construye la consulta SQL para ordenar por una columna
     * @param array $filtros Filtros de búsqueda
     * @return string Consulta SQL
     */
    function ordenarPor($filtros) {
        if (!empty($filtros['ordenarPor'])) {
            return " ORDER BY " . $filtros['ordenarPor'] . " " . strtoupper($filtros['orden']);
        }
        return " ORDER BY emp_no ASC"; // Ordenar por defecto por emp_no
    }
    ?>

Por último vamos a crear una función que se encargue de añadir la clausula LIMIT y OFFSET a la consulta. Esta función se encargará de añadir la paginación a la consulta. La función se llamará paginacion().

Código PHP para la paginación

1
2
3
4
5
6
7
8
    <?php
    /**
     * Construye la consulta SQL para paginar los resultados
     */
    function paginacion() {
        return " LIMIT :empleadosPorPagina  OFFSET :offset";
    }
    ?>

Necesitamos una función que utilice todas las funciones anteriores para construir la consulta completa. Esta función se llamará obtenerSQL($filtros, $count = false).

Código PHP para la consulta completa

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php
/**
 * Construye la consulta SQL completa
 * @param array $filtros Filtros de búsqueda
 * @param bool $count Si es true, devuelve la consulta para contar empleados
 * @return string Consulta SQL
 */
function obtenerSQL($filtros, $count = false) {
    $sql = consultaSelect($count);
    $sql .= filtroBuscar($filtros);
    $sql .= filtroEstado($filtros);
    $sql .= filtroDepartamento($filtros);
    if (!$count) $sql .= ordenarPor($filtros); // Ordena solo si no es un conteo
    $sql .= paginacion();
    return $sql;
}
?>

Ahora viene la parte más compleja, obtener el objeto statement de la consulta. Para ello vamos a crear una función que se encargue de preparar la consulta, también se tiene que encargar de realizar los binds correspondientes a los filtros y a la paginación. Esta función se llamará obtenerStatement($conexion, $query, $filtros).

Código PHP para preparar la consulta

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* Prepara la consulta SQL con MySQLi
* @param mysqli $conexion Conexión a la base de datos
* @param string $query Consulta SQL con ? en lugar de parámetros nombrados
* @param array $filtros Filtros de búsqueda, orden y paginación
* @return mysqli_stmt Consulta preparada
*/
function obtenerStatement($conexion, $query, $filtros) {
    $stmt = $conexion->prepare($query);

    if (!$stmt) {
        die("Error al preparar la consulta: " . $conexion->error);
    }

    // Armar lista de tipos y valores a enlazar
    $tipos = '';
    $valores = [];

    // filtro buscar
    $buscar = !empty($filtros['buscar']) ? $filtros['buscar'] : '%';
    $tipos .= 's';
    $valores[] = $buscar;

    // filtro departamento (si está presente)
    if (!empty($filtros['departamento'])) {
        $tipos .= 's';
        $valores[] = $filtros['departamento'];
    }

    // paginación
    $tipos .= 'ii';
    $valores[] = $filtros['empleadosPorPagina'];
    $valores[] = $filtros['offset'];

    // Enlazar parámetros dinámicamente
    $stmt->bind_param($tipos, ...$valores);

    return $stmt;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
/**
 * Prepara la consulta SQL
 * @param PDO $conexion Conexión a la base de datos
 * @param string $query Consulta SQL
 * @param array $filtros Filtros de búsqueda, orden y paginación
 * @return PDOStatement Consulta preparada
 */
function obtenerStatement($conexion, $query, $filtros) {
    // Agregar la cláusula LIMIT para la paginación
    $stmt = $conexion->prepare($query);
    // Vincular los parámetros
    // empleadosxPagina y offset siempre tienen que estar en el $filtro
    $stmt->bindValue(':empleadosPorPagina', $filtros['empleadosPorPagina'], PDO::PARAM_INT);
    $stmt->bindValue(':offset', $filtros['offset'], PDO::PARAM_INT);
    // filtro buscar
    if (!empty($filtros['buscar'])) {
        $stmt->bindParam(':buscar', $filtros['buscar'], PDO::PARAM_STR);
    } else {
        $stmt->bindValue(':buscar', '%', PDO::PARAM_STR);
    }
    // filtro departamento
    if (!empty($filtros['departamento'])) {
        $stmt->bindParam(':departamento', $filtros['departamento'], PDO::PARAM_STR);
    }
    return $stmt;
}

Y para terminar vamos a crear dos funciones, una que ejecute la consulta y me devuelva el array de empleados teniendo en cuenta todos los filtros y la segunda que me devuelva el número de empleados totales y de páginas que haría falta para mostrar todos los empleados. Estas funciones se llamarán obtenerEmpleados($conexion, $query, $filtros) y function obtenerEmpleadosCount($conexion, $query, $filtros).

Obtener empleados

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function obtenerEmpleados($conexion, $query, $filtros) {
    $query = obtenerSQL($filtros, false);
    $stmt = obtenerStatement($conexion, $query, $filtros);
    $stmt->execute();
    $resultado = $stmt->get_result();

    $empleados = [];
    while ($row = $resultado->fetch_assoc()) {
        $empleados[] = $row;
    }

    $stmt->close();
    return $empleados;
}   

function obtenerEmpleadosCount($conexion, $query, $filtros) {
    $query = obtenerSQL($filtros, true);
    $stmt = obtenerStatement($conexion, $query, $filtros);
    $stmt->execute();
    $resultado = $stmt->get_result();
    $row = $resultado->fetch_row();

    $stmt->close();
    return (int)$row[0];
}
?>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
function obtenerEmpleados($conexion, $query, $filtros) {
    $query = obtenerSQL($filtros, false);
    $stmt = obtenerStatement($conexion, $query, $filtros);
    // Asignar los valores a los parámetros
    $stmt->execute();
    // Obtener los resultados
    $empleados = [];
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        $empleados[] = $row;
    }
    return $empleados;
}

function obtenerEmpleadosCount($conexion, $query, $filtros) {
    $query = obtenerSQL($filtros, true);
    $stmt = obtenerStatement($conexion, $query, $filtros);
    // Asignar los valores a los parámetros
    $stmt->execute();
    // Obtener el total de empleados
    return $stmt->fetchColumn();
}
?>

2. Modificar obtenerEmpleados.php para permitir la paginación:

Necesitamos agregar la lógica de paginación en el archivo obtenerEmpleados.php. Primero, definimos cuántos empleados mostrar por página (por ejemplo, 10 empleados por página) y luego calculamos la página actual y el número total de páginas.

Modificación en obtenerEmpleados.php:

Código PHP para paginación

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
// include de las funciones de conexión a la base de datos
include "../lib/bd_mysqli.php"; // Este debe devolver una conexión mysqli
const EMPLEADOS_POR_PAGINA = 10;
require_once 'obtenerEmpleadosFuncs_mysqli.php';

$filtros = json_decode(file_get_contents('php://input'), true);
$paginaActual = isset($filtros['pagina']) ? (int)$filtros['pagina'] : 1;
$offset = ($paginaActual - 1) * EMPLEADOS_POR_PAGINA;

$conexion = obtenerConexion(); // mysqli

$empleados = obtenerEmpleados($conexion, [
    'empleadosPorPagina' => EMPLEADOS_POR_PAGINA,
    'offset' => $offset,
    'buscar' => '%' . $filtros['buscar'] . '%',
    'departamento' => $filtros['departamento'],
    'estado' => $filtros['estado']
]);

$count = obtenerEmpleadosCount($conexion, [
    'empleadosPorPagina' => 1000000,
    'offset' => 0,
    'buscar' => '%' . $filtros['buscar'] . '%',
    'departamento' => $filtros['departamento'],
    'estado' => $filtros['estado']
]);

echo json_encode([
    'empleados' => $empleados,
    'totalPaginas' => ceil($count / EMPLEADOS_POR_PAGINA),
    'paginaActual' => $paginaActual
]);

$conexion->close();
?>

```php lineenums="1" <?php // include de las funciones de conexión a la base de datos include "../lib/bd.php"; // constante para manejar el número de empleados por página const EMPLEADOS_POR_PAGINA = 10; // libreria con las funciones para obtener los empleados require_once 'obtenerEmpleadosFuncs.php';

// debe llegar un json con información de los filtros $filtros = json_decode(file_get_contents('php://input'), true); $paginaActual = isset($filtros['pagina']) ? (int)$filtros['pagina'] : 1; $offset = ($paginaActual - 1) * EMPLEADOS_POR_PAGINA; // obtener el siguiente empleado a obtener $conexion = obtenerConexion(); // obtener un array con los empleados que cumplen con los filtros $empleados = obtenerEmpleados($conexion, [ 'empleadosPorPagina' => EMPLEADOS_POR_PAGINA, 'offset' => $offset, 'buscar' => '%' . $filtros['buscar'] . '%', 'departamento' => $filtros['departamento'], 'estado' => $filtros['estado'] ]);

// Obtener el total de empleados para la paginación $count = obtenerEmpleadosCount($conexion, [ 'empleadosPorPagina' => 1000000, 'offset' => 0, 'buscar' => '%' . $filtros['buscar'] . '%', 'departamento' => $filtros['departamento'], 'estado' => $filtros['estado'] ]);

echo json_encode([ 'empleados' => $empleados, 'totalPaginas' => ceil($count / EMPLEADOS_POR_PAGINA), 'paginaActual' => $paginaActual ]);

$conexion = null; ?> ```


3. Modificar JavaScript para manejar la paginación:

Necesitamos agregar botones de navegación de páginas (anterior, siguiente y números de página) y enviar los parámetros adecuados de paginación al servidor.

Modificación en employees.php (JavaScript):

Modificación para tener en cuenta la paginación

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
function obtenerEmpleadosConFiltros(pagina = 1, couluma = null, orden = null) {
    const buscar = document.getElementById('buscarEmpleado').value;
    const estado = document.getElementById('filtroEstado').value;
    const departamento = document.getElementById('filtroDepartamento').value;

    // actualizamos las variables globales de ordena
    if (couluma) {
        ordenColumna = couluma;
    }
    if (orden) {
        ordenTipo = orden;
    }

    if (pagina) {
        paginaActual = pagina;
    }

    // Crear el objeto con los filtros
    const filtros = {
        buscar: buscar,
        estado: estado,
        departamento: departamento,
        pagina: pagina,
        ordenarPor: couluma,  // Columna por la que se va a ordenar
        orden: orden         // Orden (ascendente o descendente)
    };

    // Realizar la solicitud AJAX
    fetch('obtenerEmpleados.php', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(filtros)
    })
    .then(response => response.json())
    .then(data => {
        // Limpiar la tabla antes de agregar los nuevos empleados
        const tablaCuerpo = document.getElementById('empleados').getElementsByTagName('tbody')[0];
        tablaCuerpo.innerHTML = '';

        // Agregar los empleados a la tabla
        data.empleados.forEach(empleado => {
            const row = tablaCuerpo.insertRow();
            row.innerHTML = `
                <td>${empleado.emp_no}</td>
                <td>${empleado.first_name}</td>
                <td>${empleado.last_name}</td>
                <td>${empleado.birth_date}</td>
                <td>${empleado.hire_date}</td>
                <td>${empleado.gender}</td>
                <td><button onclick="editarEmpleado(${empleado.emp_no})">Editar</button></td>
                <td><button onclick="eliminarEmpleado(${empleado.emp_no})">Eliminar</button></td>
            `;
        });
        // Actualizar los botones de paginación
        actualizarPaginacion(data.totalPaginas, data.paginaActual);
    });
}

Código JavaScript para botones de paginación

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Función para actualizar los botones de paginación
function actualizarPaginacion(totalPaginas, paginaActual) {
    const paginacion = document.getElementById('paginacion');
    paginacion.innerHTML = '';

    // Botón para ir a la primera página
    const btnPrimera = document.createElement('button');
    btnPrimera.textContent = 'Primera';
    btnPrimera.onclick = () => obtenerEmpleadosConFiltros(1, ordenColumna, ordenTipo);
    if (paginaActual === 1) {
        btnPrimera.disabled = true; // Deshabilitar si ya estamos en la primera página
    }
    paginacion.appendChild(btnPrimera);

    // Botón para ir a la anterior página
    const btnAnterior = document.createElement('button');
    btnAnterior.textContent = 'Anterior';
    btnAnterior.onclick = () => obtenerEmpleadosConFiltros(paginaActual - 1, ordenColumna, ordenTipo);
    if (paginaActual === 1) btnAnterior.disabled = true; // Deshabilitar si ya estamos en la primera página
    paginacion.appendChild(btnAnterior);

    // Información de la página actual
    const infoPagina = document.createElement('span');
    infoPagina.textContent = `Página ${paginaActual} de ${totalPaginas}`;
    paginacion.appendChild(infoPagina);

    // Botón para ir a la siguiente página
    const btnSiguiente = document.createElement('button');
    btnSiguiente.textContent = 'Siguiente';
    btnSiguiente.onclick = () => obtenerEmpleadosConFiltros(paginaActual + 1, ordenColumna, ordenTipo);
    if (paginaActual === totalPaginas) btnSiguiente.disabled = true; // Deshabilitar si ya estamos en la última página
    paginacion.appendChild(btnSiguiente);

    // Botón para ir a la última página
    const btnUltima = document.createElement('button');
    btnUltima.textContent = 'Última';
    btnUltima.onclick = () => obtenerEmpleadosConFiltros(totalPaginas, ordenColumna, ordenTipo);
    if (paginaActual === totalPaginas) btnUltima.disabled = true; // Deshabilitar si ya estamos en la última página
    paginacion.appendChild(btnUltima);
}

Por último añadimos el código necesario para que al cargar la página se llene la tabla de empleados con los empleados que tenemos en la base de datos. Para ello vamos a llamar a la función obtenerEmpleadosConFiltros() al cargar la página.

Código JavaScript para cargar la tabla de empleados

1
2
3
4
// Cargar los empleados al cargar la página
document.addEventListener('DOMContentLoaded', () => {
    obtenerEmpleadosConFiltros(1, ordenColumna, ordenTipo);
});

Explicación:

  1. obtenerEmpleadosConFiltros(pagina): Ahora la función toma un parámetro pagina, que indica qué página mostrar. Esta función realiza la solicitud AJAX y actualiza la tabla de empleados.
  2. actualizarPaginacion(totalPaginas, paginaActual): Esta función crea los botones de paginación (primera, anterior, siguiente y última y número de página actual) dinámicamente. Al hacer clic en un botón se vuelve a cargar.

3. Modificar HTML para la paginación:

En el HTML de la página employees.php, vamos a agregar un contenedor para los botones de paginación.

<!-- Contenedor para los botones de paginación -->
<div id="paginacion" class="paginacion">
    <!-- Los botones de paginación se llenarán dinámicamente con JavaScript -->
</div>

CSS para la paginación:

/* Estilos para los botones de paginación */
.paginacion {
    margin-top: 20px;
    text-align: center;
}

.paginacion button {
    padding: 10px;
    margin: 5px;
    font-size: 1rem;
    cursor: pointer;
}

.paginacion button:disabled {
    background-color: #ddd;
    cursor: not-allowed;
}

Resumen de la mejora:

  1. Paginación: Los empleados se cargan en páginas limitadas, mejorando el rendimiento cuando hay muchos registros.
  2. Botones de navegación: Se añaden botones de anterior, siguiente y números de página para navegar entre las páginas.
  3. AJAX: Usamos AJAX para obtener los empleados de manera dinámica y actualizar la tabla sin recargar la página.