Software y sistemas

Sistema de estudiantes y calificaciones con PHP y MySQL

Hoy veremos un sistema muy básico para gestionar estudiantes, materias y notas/calificaciones usando el lenguaje de programación PHP con la base de datos MySQL.

Más allá de un sistema es como un ejercicio. Se trata de:

  • Registrar alumnos
  • Registrar materias
  • Agregar calificaciones de estudiantes por cada materia

Es como la versión básica de un control escolar.

Conexión con MySQL

He usado MySQL como motor de base de datos, a través de la extensión MySQLi de PHP. El archivo de conexión se ve así:

<?php
$host = "localhost";
$usuario = "root";
$contrasenia = "";
$base_de_datos = "escuela";
$mysqli = new mysqli($host, $usuario, $contrasenia, $base_de_datos);
if ($mysqli->connect_errno) {
    echo "Falló la conexión a MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}

Recuerda cambiar tus credenciales según sea tu caso. Y no olvides crear la base de datos. Las tablas quedan como a continuación:

CREATE TABLE estudiantes(
    id bigint unsigned not null primary key auto_increment,
    nombre varchar(255) not null,
    grupo varchar(255) not null
);

CREATE TABLE materias(
    id bigint unsigned not null primary key auto_increment,
    nombre varchar(255) not null
);


CREATE TABLE notas_estudiantes_materias(
    id bigint unsigned not null primary key auto_increment,
    id_estudiante bigint unsigned not null,
    id_materia bigint unsigned not null,
    puntaje decimal(9,2) not null,
    foreign key (id_estudiante) references estudiantes(id) on delete cascade on update cascade,
    foreign key (id_materia) references materias(id) on delete cascade on update cascade
);

El esquema indica que hay 3 tablas. La primera guarda información de los estudiantes, como lo son el id, nombre y grupo. La segunda tabla es la tabla de materias y se encarga de guardar el nombre de las mismas.

Finalmente la última tabla se encarga de unir las 2 primeras tablas, es decir, la de estudiantes y materias, además de agregar un puntaje que indica la calificación o nota del alumno. La misma también se encarga de relacionar las dos tablas con foreign key.

Plantilla de Bootstrap

He diseñado este sistema con Bootstrap para evitar darle estilos manuales y ahorrar tiempo. También he separado toda la plantilla en un encabezado, pie y contenido. El contenido depende de cada página.

Por lo tanto el encabezado queda así:

<!DOCTYPE html>
<html lang="es">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="bootstrap.min.css">

    <style>
        body {
            padding-top: 70px;
        }
    </style>
    <title>Control de notas</title>
</head>

<body>
    <nav class="navbar navbar-expand-md navbar-dark bg-success fixed-top">
        <a class="navbar-brand" href="https://parzibyte.me/blog">Control de notas</a>
        <div class="collapse navbar-collapse">
            <ul class="navbar-nav mr-auto">
                <li class="nav-item">
                    <a class="nav-link" href="mostrar_estudiantes.php">Estudiantes</a>
                </li>

                <li class="nav-item">
                    <a class="nav-link" href="mostrar_materias.php">Materias</a>
                </li>
            </ul>
        </div>
        <div class="collapse navbar-collapse" id="menu">
            <ul class="navbar-nav ml-auto">
                <li class="nav-item active">
                    <a class="nav-link" href="//parzibyte.me/blog">Soporte y ayuda&nbsp;<i class="fa fa-hands-helping"></i></a>
                </li>
            </ul>
        </div>
    </nav>
    <main class="container-fluid">

Y el pie queda así:

<footer class="px-2 py-2 fixed-bottom bg-dark">
    <span class="text-muted">Simple proyecto de sistema escolar creado por
        <a class="text-white" href="//parzibyte.me/blog">Parzibyte</a>
        &nbsp;|&nbsp;
        <a target="_blank" class="text-white" href="https://github.com/parzibyte/escuela_php">
            Ver código fuente
        </a>
    </span>
</footer>
</main>

</body>

</html>

Cuando una página quiere usar esto, simplemente usa include de PHP.

Estudiantes

Tenemos un “controlador” que se encarga de la gestión de los estudiantes. No es como tal un controlador ni un modelo, es una clase que encapsula todos los métodos; pero usamos Programación orientada a objetos con PHP:

<?php
class Estudiante
{
    private $nombre, $grupo, $id;

    public function __construct($nombre, $grupo, $id = null)
    {
        $this->nombre = $nombre;
        $this->grupo = $grupo;
        if ($id) {
            $this->id = $id;
        }
    }

    public function guardar()
    {
        global $mysqli;
        $sentencia = $mysqli->prepare("INSERT INTO estudiantes
            (nombre, grupo)
                VALUES
                (?, ?)");
        $sentencia->bind_param("ss", $this->nombre, $this->grupo);
        $sentencia->execute();
    }

    public static function obtener()
    {
        global $mysqli;
        $resultado = $mysqli->query("SELECT id, nombre, grupo FROM estudiantes");
        return $resultado->fetch_all(MYSQLI_ASSOC);
    }
    public static function obtenerUno($id)
    {
        global $mysqli;
        $sentencia = $mysqli->prepare("SELECT id, nombre, grupo FROM estudiantes WHERE id = ?");
        $sentencia->bind_param("i", $id);
        $sentencia->execute();
        $resultado = $sentencia->get_result();
        return $resultado->fetch_object();
    }
    public function actualizar()
    {
        global $mysqli;
        $sentencia = $mysqli->prepare("update estudiantes set nombre = ?, grupo = ? where id = ?");
        $sentencia->bind_param("ssi", $this->nombre, $this->grupo, $this->id);
        $sentencia->execute();
    }

    public static function eliminar($id)
    {
        global $mysqli;
        $sentencia = $mysqli->prepare("DELETE FROM estudiantes WHERE id = ?");
        $sentencia->bind_param("i", $id);
        $sentencia->execute();
    }
}

Tenemos varias funciones. Por ejemplo, obtenerUno regresa un estudiante de la base de datos a partir de su id (método estático). El método eliminar se encarga de borrar el estudiante a partir del id; el de actualizar se encarga de hacer la operación update y el de guardar se encarga de insertarlo en la base de datos.

También existe el método obtener que devuelve todos los estudiantes como un arreglo usando fetch_all.

Todos los métodos que mencioné anteriormente ya están en el post de CRUD con MySQLi y PHP.

Cada que se requiere hacer una operación, se crea una instancia de la clase y se invoca al método; o si el método es estático simplemente se invoca sin instanciar.

Crear estudiante

Para crearlo se reciben los datos a través de un formulario, se crea una instancia y se guarda en la base de datos de MySQL:

<?php
include_once "conexion.php";
include_once "Estudiante.php";
$estudiante = new Estudiante($_POST["nombre"], $_POST["grupo"]);
$estudiante->guardar();
header("Location: mostrar_estudiantes.php");

Como ves, es muy simple gestionar estudiantes en este pequeño sistema escolar con PHP pues creamos un objeto, invocamos a guardar y luego hacemos una redirección.

Mostrar estudiantes

Sistema escolar PHP – Mostrar estudiantes

Para mostrarlos vemos el método usado para listarlos. Obtenemos todos los estudiantes y luego creamos una tabla HTML que muestra 3 botones: editar, eliminar y administrar las notas.

<?php
include_once "conexion.php";
include_once "encabezado.php";
include_once "Estudiante.php";
$estudiantes = Estudiante::obtener();
?>
<div class="row">
    <div class="col-12">
        <h1>Listado de estudiantes</h1>
        <a href="formulario_registro_estudiante.php" class="btn btn-info my-2">Nuevo</a>
    </div>
    <div class="col-12 table-responsive">
        <table class="table table-bordered">
            <thead>
                <tr>
                    <th>Nombre</th>
                    <th>Grupo</th>
                    <th>Notas</th>
                    <th>Editar</th>
                    <th>Eliminar</th>
                </tr>
            </thead>
            <tbody>
                <?php foreach ($estudiantes as $estudiante) { ?>
                    <tr>
                        <td><?php echo $estudiante["nombre"] ?></td>
                        <td><?php echo $estudiante["grupo"] ?></td>
                        <td>
                            <a href="notas_estudiante.php?id=<?php echo $estudiante["id"] ?>" class="btn btn-info">
                                Notas
                            </a>
                        </td>
                        <td>
                            <a href="editar_estudiante.php?id=<?php echo $estudiante["id"] ?>" class="btn btn-warning">
                                Editar
                            </a>
                        </td>
                        <td>
                            <a href="eliminar_estudiante.php?id=<?php echo $estudiante["id"] ?>" class="btn btn-danger">
                                Eliminar
                            </a>
                        </td>
                    </tr>
                <?php } ?>
            </tbody>
        </table>
    </div>
</div>
<?php
include_once "pie.php";

En este caso invocamos al método obtener, pero directamente desde la clase, pues es un método estático. Lo que importa es que en la tabla tenemos los 3 botones que en realidad son enlaces, y cada uno lleva respectivamente a notas_estudiante.php, editar_estudiante.php y eliminar_estudiante.php pasándole el id a través de la URL.

Editar estudiante

Para editar mostramos el formulario y lo rellenamos con los datos existentes:

<?php
include_once "conexion.php";
include_once "Estudiante.php";
include_once "encabezado.php";
$estudiante = Estudiante::obtenerUno($_GET["id"]);
?>
<div class="row">
    <div class="col-12">
        <h1>Editar estudiante</h1>
        <form action="actualizar_estudiante.php" method="POST">
            <input type="hidden" name="id" value="<?php echo $_GET["id"] ?>">
            <div class="form-group">
                <label for="nombre">Nombre</label>
                <input value="<?php echo $estudiante->nombre ?>" name="nombre" required type="text" id="nombre" class="form-control" placeholder="Nombre">
            </div>
            <div class="form-group">
                <label for="grupo">Grupo</label>
                <input value="<?php echo $estudiante->grupo ?>" name="grupo" required type="text" id="grupo" class="form-control" placeholder="Grupo">
            </div>
            <div class="form-group">
                <button class="btn btn-success" type="submit">Guardar</button>
            </div>
        </form>
    </div>
</div>
<?php include_once "pie.php" ?>

En este caso estamos invocando al método obtenerUno y rellenamos los valores usando el atributo value de los input de HTML. El action del formulario es actualizar_estudiante.php que es donde este sistema escolar actualiza los datos del alumno:

<?php
include_once "conexion.php";
include_once "Estudiante.php";
$estudiante = new Estudiante($_POST["nombre"], $_POST["grupo"], $_POST["id"]);
$estudiante->actualizar();
header("Location: mostrar_estudiantes.php");

Ahora se está invocando al método actualizar y después de eso se redirecciona al listado.

Eliminar estudiante

Para eliminar simplemente se recupera el id de la URL y se borra el registro de la base de datos:

<?php
include_once "conexion.php";
include_once "Estudiante.php";
Estudiante::eliminar($_GET["id"]);
header("Location: mostrar_estudiantes.php");

Gestión de materias

La administración de materias en este sistema es muy similar a la de estudiantes, así que para no hacer más largo el post, dejaré únicamente la clase que se encarga de gestionarlas. Obviamente puedes explorar todo el código que te dejaré al final del post.

<?php
class Materia
{
    private $nombre, $id;

    public function __construct($nombre, $id = null)
    {
        $this->nombre = $nombre;
        if ($id) {
            $this->id = $id;
        }
    }

    public function guardar()
    {
        global $mysqli;
        $sentencia = $mysqli->prepare("INSERT INTO materias
            (nombre)
                VALUES
                (?)");
        $sentencia->bind_param("s", $this->nombre);
        $sentencia->execute();
    }

    public static function obtener()
    {
        global $mysqli;
        $resultado = $mysqli->query("SELECT id, nombre FROM materias");
        return $resultado->fetch_all(MYSQLI_ASSOC);
    }
    public static function obtenerUna($id)
    {
        global $mysqli;
        $sentencia = $mysqli->prepare("SELECT id, nombre FROM materias WHERE id = ?");
        $sentencia->bind_param("i", $id);
        $sentencia->execute();
        $resultado = $sentencia->get_result();
        return $resultado->fetch_object();
    }
    public function actualizar()
    {
        global $mysqli;
        $sentencia = $mysqli->prepare("update materias set nombre = ? where id = ?");
        $sentencia->bind_param("si", $this->nombre, $this->id);
        $sentencia->execute();
    }

    public static function eliminar($id)
    {
        global $mysqli;
        $sentencia = $mysqli->prepare("DELETE FROM materias WHERE id = ?");
        $sentencia->bind_param("i", $id);
        $sentencia->execute();
    }
}

Ahora pasemos a algo importante, las notas o calificaciones.

Notas de estudiantes

Notas de estudiante (Calificaciones) con PHP y MySQL

Las notas o calificaciones en este caso no llevan un período relacionado, solo materias. Así que por cada materia que exista en la base de datos hay que mostrar un campo que solicite la calificación, creando un formulario dinámico.

El promedio se calcula llevando una sumatoria de los puntajes y dividiendo entre la longitud del arreglo.

Al final de la tabla se muestra el promedio de todas las calificaciones calculado de manera automática. Las calificaciones se pueden editar en cualquier momento y son por cada estudiante.

<?php
class Nota
{
    private $puntaje, $idEstudiante, $idMateria;

    public function __construct($puntaje, $idEstudiante, $idMateria)
    {
        $this->puntaje = $puntaje;
        $this->idEstudiante = $idEstudiante;
        $this->idMateria = $idMateria;
    }

    public function guardar()
    {
        global $mysqli;
        // La eliminamos en caso de que exista
        $this->eliminar();
        // Y siempre la insertamos. No importa si es la primera vez o es una actualización
        $sentencia = $mysqli->prepare("INSERT INTO notas_estudiantes_materias
            (id_estudiante, id_materia, puntaje)
                VALUES
                (?, ?, ?)");
        $sentencia->bind_param("ssd", $this->idEstudiante, $this->idMateria, $this->puntaje);
        $sentencia->execute();
    }

    public static function obtenerDeEstudiante($idEstudiante)
    {
        global $mysqli;
        $sentencia = $mysqli->prepare("SELECT id, id_estudiante, id_materia, puntaje FROM notas_estudiantes_materias WHERE id_estudiante = ?");
        $sentencia->bind_param("i", $idEstudiante);
        $sentencia->execute();
        $resultado = $sentencia->get_result();
        return $resultado->fetch_all(MYSQLI_ASSOC);
    }

    public static function combinar($materias, $notas)
    {
        for ($x = 0; $x < count($materias); $x++) {
            $materias[$x]["puntaje"] = self::obtenerCalificacion($notas, $materias[$x]["id"]);
        }
        return $materias;
    }

    private static function obtenerCalificacion($notas, $idMateria)
    {
        foreach ($notas as $nota) {
            if (intval($nota["id_materia"]) === intval($idMateria)) {
                return $nota["puntaje"];
            }
        }
        return 0;
    }


    public function eliminar()
    {
        global $mysqli;
        $sentencia = $mysqli->prepare("DELETE FROM notas_estudiantes_materias WHERE id_estudiante = ? AND id_materia = ?");
        $sentencia->bind_param("ii", $this->idEstudiante, $this->idMateria);
        $sentencia->execute();
    }
}

Guardar las calificaciones de estudiante

En este caso observamos el método guardar, que primero elimina todas las notas anteriores pertenecientes a ese alumno. Esto se hace así porque:

  1. Si es la primera vez que las notas se guardan, no se va a eliminar nada
  2. Si es la segunda vez que las notas se guardan, se van a eliminar las anteriores e insertar las nuevas, haciendo algo como un “update”.

Se hace de este modo porque así no se tiene que detectar si se debe realizar un insert o un update; siempre se hace un insert y se elimina lo anterior.

Obtener calificaciones

Para mostrar las notas en la tabla, con los datos rellenos, se usa el siguiente código. En el mismo observamos que primero obtenemos los datos del estudiante, y luego todas las materias.

Luego obtenemos las notas y finalmente en la línea 10 obtenemos las materias con calificación.

El método combinar es importante y se encarga de combinar las materias con notas. En este caso si ya existe una nota para esa materia, regresa esa nota. Caso contrario, regresa 0. Así que la primera vez que se visite la página, todos los campos estarán en 0, y si luego se guarda algún dato, estará así para la siguiente ocasión.

<?php
include_once "conexion.php";
include_once "encabezado.php";
include_once "Estudiante.php";
include_once "Materia.php";
include_once "Nota.php";
$estudiante = Estudiante::obtenerUno($_GET["id"]);
$materias = Materia::obtener();
$notas = Nota::obtenerDeEstudiante($estudiante->id);
$materiasConCalificacion = Nota::combinar($materias, $notas);
?>
<div class="row">
    <div class="col-12">
        <h1>Notas de <?php echo $estudiante->nombre ?></h1>
    </div>
    <div class="col-12 table-responsive">
        <table class="table">
            <thead>
                <tr>
                    <th>Materia</th>
                    <th>Puntaje</th>
                </tr>
            </thead>
            <tbody>
                <?php
                $sumatoria = 0;
                foreach ($materiasConCalificacion as $materia) {
                    $sumatoria += $materia["puntaje"];
                ?>
                    <tr>
                        <td>
                            <?php echo $materia["nombre"] ?>
                        </td>
                        <td>
                            <form action="modificar_nota.php" method="POST" class="form-inline">
                                <input type="hidden" value="<?php echo $estudiante->id ?>" name="id_estudiante">
                                <input type="hidden" value="<?php echo $materia["id"] ?>" name="id_materia">
                                <input value="<?php echo $materia["puntaje"] ?>" required min="0" name="puntaje" placeholder="Escriba la calificación" type="number" class="form-control">
                                <button class="btn btn-success mx-2">Guardar</button>
                            </form>
                        </td>
                    </tr>
                <?php } ?>
            </tbody>
            <tfoot>
                <tr>
                    <td>Promedio</td>
                    <td>
                        <strong>
                            <?php
                            $promedio = $sumatoria / count($materias);
                            echo $promedio;
                            ?>
                        </strong>
                    </td>
                </tr>
            </tfoot>
        </table>
    </div>
</div>
<?php
include_once "pie.php";

También es importante notar el método que modifica la calificación o nota, llamado modificar_nota.php al que le estamos enviando el id de estudiante, id de materia y el nuevo puntaje.

El código del mismo se ve así:

<?php
include_once "conexion.php";
include_once "Nota.php";
$nota = new Nota($_POST["puntaje"], $_POST["id_estudiante"], $_POST["id_materia"]);
$nota->guardar();
header("Location: notas_estudiante.php?id=" . $_POST["id_estudiante"]);

Estamos usando de nuevo a la clase Nota, que no es más que la encarga de gestionar todo lo relacionado a las calificaciones en este sistema de estudiantes, materias y notas con PHP y MySQL.

Código fuente y conclusión

Así es como termina la presentación de este pequeño sistema de escuela en PHP. El código fuente lo encuentras en mi GitHub.

Te invito a ver otros proyectos que he creado, a leer más sobre PHP y sobre MySQL.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Ver comentarios

Entradas recientes

Creador de credenciales web – Aplicación gratuita

Hoy te voy a presentar un creador de credenciales que acabo de programar y que…

1 semana hace

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

2 semanas hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

2 semanas hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

2 semanas hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

2 semanas hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

2 semanas hace

Esta web usa cookies.