📝 cuadernos_examen_informatica
← Volver

Cuadernos de Repaso – Examen de Informática

Este documento contiene 6 cuadernos de 10 ejercicios cada uno (60 ejercicios en total),
inspirados en el examen del año pasado (caso práctico + preguntas de desarrollo y código).

Cada cuaderno:

Cómo estudiar con este documento: no hace falta memorizar el código exacto.
Lo importante es entender el concepto (qué es una clase, qué es una API REST, qué hace
cada línea), porque en el examen real los datos del enunciado cambiarán, pero el patrón
de la solución es siempre el mismo
.


Índice general

Cuaderno Caso práctico Estado
1 Gestión de préstamos de la Biblioteca Universitaria ✅ Desarrollado
2 Sistema de Cita Previa para trámites administrativos ⏳ Pendiente
3 Gestión de inscripciones a cursos de formación del personal ⏳ Pendiente
4 Sistema de gestión de incidencias informáticas (CAU) ⏳ Pendiente
5 Sistema de reserva de aulas y espacios comunes ⏳ Pendiente
6 Sistema de control de presencia (fichaje) del personal ⏳ Pendiente

Plantilla de los 10 ejercicios (se repite en cada cuaderno)

Todos los cuadernos siguen el mismo esqueleto de 10 preguntas, para que repases los 7 temas
una y otra vez con casos distintos:

Ejercicio Tema(s) del temario Qué se trabaja
1 Tema 1 Tipos de datos y operadores
2 Tema 1 Bucles, condicionales y recursividad
3 Tema 1 y 4 Vectores/registros, clases y herencia (POO)
4 Tema 2 Arquitectura cliente/servidor multicapa
5 Tema 2 Git, metodologías ágiles, pruebas y construcción de software
6 Tema 3 HTML y XML
7 Tema 4 Implementación de clases en PHP o Java
8 Tema 5 Acceso a bases de datos (JDBC/PDO y ORM)
9 Tema 6 API RESTful y JSON
10 Tema 7 JavaScript/TypeScript y frameworks SPA

Glosario básico (léelo antes de empezar)

Estos conceptos aparecen una y otra vez. Si los tienes claros, entender cada ejercicio es mucho
más fácil. No es necesario aprenderlos de memoria ahora: vuelve aquí cuando un ejercicio use un
término que no recuerdes.


CUADERNO 1 – Gestión de préstamos de la Biblioteca Universitaria

Caso práctico

La Biblioteca de una universidad pública quiere informatizar la gestión de préstamos de libros.
La unidad de informática define el siguiente modelo de datos:

Usuario (DNI, NRP, Nombre, Apellidos, TipoUsuario)
   TipoUsuario ∈ {Estudiante, PDI, PAS}
   (PDI = Personal Docente e Investigador; PAS = Personal de Administración y Servicios)

Libro (ISBN, Titulo, Autor, NumEjemplares, EjemplaresDisponibles)

Prestamo (IdPrestamo, DNI, ISBN, FechaPrestamo, FechaDevolucionPrevista,
          FechaDevolucionReal, Estado)
   Estado ∈ {activo, devuelto}

Reserva (IdReserva, DNI, ISBN, FechaReserva, Estado)
   Estado ∈ {pendiente, atendida, cancelada}

Reglas de negocio importantes (se usarán en varios ejercicios):


Ejercicio 1 (Tema 1 — Tipos de datos y operadores)

Enunciado

Indique qué tipo de dato básico utilizaría para representar cada uno de los campos de las
entidades Usuario y Prestamo del modelo de datos, justificando brevemente la elección.
A continuación, escriba en PHP la declaración de las variables necesarias para representar
un préstamo concreto y, utilizando operadores aritméticos, relacionales y lógicos, calcule
el número de días de retraso de ese préstamo y determine si está vencido (es decir, sigue
activo y su fecha de devolución prevista ya ha pasado).

Desarrollo y explicación

a) Tipos de datos para cada campo

Campo Tipo de dato Por qué
DNI, NRP cadena de texto (string) Aunque parecen "números", incluyen letras (la letra del DNI) o ceros a la izquierda que no deben perderse, y nunca se usan en operaciones aritméticas.
Nombre, Apellidos cadena de texto (string) Es texto.
TipoUsuario cadena de texto (string) o un tipo enumerado Solo puede tomar un valor de una lista cerrada ("Estudiante", "PDI", "PAS").
ISBN cadena de texto (string) Tiene guiones y se trata como código, no como número.
Titulo, Autor cadena de texto (string) Es texto.
NumEjemplares, EjemplaresDisponibles entero (int) Son cantidades enteras de libros (no tiene sentido "2,5 libros").
FechaPrestamo, FechaDevolucionPrevista, FechaDevolucionReal fecha (date) Son fechas; necesitamos poder compararlas y calcular diferencias en días.
Estado cadena de texto (string) o enumerado Lista cerrada de valores ("activo", "devuelto").

b) Código PHP

<?php
// --- Declaración de variables que representan UN préstamo concreto ---

$dni  = "12345678A";          // string: identifica al usuario que tiene el libro
$isbn = "978-3-16-148410-0";  // string: identifica el libro prestado

// DateTime es un tipo especial de PHP para trabajar con fechas
$fechaPrestamo           = new DateTime("2025-05-01");
$fechaDevolucionPrevista = new DateTime("2025-05-08");
$fechaDevolucionReal     = null;   // null = "sin valor": todavía no se ha devuelto

$estado = "activo";  // string

// Fecha actual del sistema
$hoy = new DateTime();

// --- Operador lógico (&&) ---
// "El préstamo sigue activo" SI Y SOLO SI:
//   - no tiene fecha de devolución real (===  es "igual y del mismo tipo")
//   - Y su estado es "activo"
$prestamoActivo = ($fechaDevolucionReal === null) && ($estado === "activo");

// --- Cálculo de días de retraso (operadores aritméticos / comparación) ---
// diff() calcula la diferencia entre dos fechas
$diferencia = $hoy->diff($fechaDevolucionPrevista);

// Operador relacional ">" : ¿la fecha de hoy es POSTERIOR a la prevista?
// Operador ternario "? :" es un "if" resumido en una sola línea:
//   condicion ? valor_si_verdadero : valor_si_falso
$diasRetraso = ($hoy > $fechaDevolucionPrevista) ? $diferencia->days : 0;

// --- Operador lógico (&&) combinado con relacional (>) ---
$vencido = $prestamoActivo && ($diasRetraso > 0);

echo "Días de retraso: " . $diasRetraso . "\n";
echo "¿Préstamo vencido?: " . ($vencido ? "Sí" : "No") . "\n";

Explicación línea por línea de lo más importante:


Ejercicio 2 (Tema 1 — Bucles, condicionales y recursividad)

Enunciado

Dado un array (vector) con varios préstamos, cada uno representado como un registro con los
campos dni, isbn, fechaDevolucionPrevista y estado, escriba un programa en PHP que
recorra el array y, mediante una instrucción condicional, muestre por pantalla únicamente los
préstamos vencidos. A continuación, defina el concepto de recursividad y, como ejemplo,
implemente una función recursiva que recorra ese mismo array y devuelva la fecha de
devolución prevista más antigua
.

Desarrollo y explicación

a) Recorrido con bucle + condicional

<?php
$prestamos = [
    ["dni" => "111", "isbn" => "AAA", "fechaDevolucionPrevista" => "2025-05-01", "estado" => "activo"],
    ["dni" => "222", "isbn" => "BBB", "fechaDevolucionPrevista" => "2026-12-01", "estado" => "activo"],
    ["dni" => "333", "isbn" => "CCC", "fechaDevolucionPrevista" => "2025-06-01", "estado" => "devuelto"],
];

$hoy = new DateTime();

// foreach recorre CADA elemento del array, uno a uno
foreach ($prestamos as $prestamo) {

    $fechaPrevista = new DateTime($prestamo["fechaDevolucionPrevista"]);

    // Condición: sigue activo Y su fecha prevista ya ha pasado
    if ($prestamo["estado"] === "activo" && $fechaPrevista < $hoy) {
        echo "Préstamo vencido -> DNI: {$prestamo['dni']}, Libro: {$prestamo['isbn']}\n";
    }
}

Explicación:

b) Recursividad

La recursividad consiste en que una función se llama a sí misma para resolver un
problema, dividiéndolo en una versión más pequeña del mismo problema. Toda función recursiva
necesita:

  1. Un caso base: una condición sencilla que se puede resolver directamente, sin más
    llamadas, y que detiene la recursividad.
  2. Un caso recursivo: donde la función se llama a sí misma con una versión "más pequeña"
    del problema (por ejemplo, el resto del array, sin el primer elemento).

Si no existiera el caso base, la función se llamaría a sí misma infinitamente y el programa
fallaría (error de "desbordamiento de pila").

Ejemplo: función recursiva que encuentra la fecha de devolución prevista más antigua del array:

<?php
function fechaMasAntigua(array $prestamos, int $indice = 0, ?DateTime $minima = null): ?DateTime {

    // --- CASO BASE ---
    // Si ya hemos recorrido todos los elementos, devolvemos el resultado acumulado
    if ($indice >= count($prestamos)) {
        return $minima;
    }

    $fechaActual = new DateTime($prestamos[$indice]["fechaDevolucionPrevista"]);

    // Si es la primera vuelta (minima es null) o esta fecha es anterior a la mínima encontrada
    if ($minima === null || $fechaActual < $minima) {
        $minima = $fechaActual;
    }

    // --- CASO RECURSIVO ---
    // Llamamos a la misma función, pero avanzando al siguiente elemento (indice + 1)
    return fechaMasAntigua($prestamos, $indice + 1, $minima);
}

$fecha = fechaMasAntigua($prestamos);
echo "La fecha de devolución prevista más antigua es: " . $fecha->format("Y-m-d") . "\n";

Explicación:

En la práctica, para este problema concreto sería más sencillo usar un bucle foreach (como
en el apartado a). La recursividad se usa sobre todo cuando un problema se divide
naturalmente en subproblemas más pequeños de la misma forma (por ejemplo, recorrer árboles
de carpetas, o el clásico cálculo del factorial: factorial(n) = n * factorial(n-1), con caso
base factorial(0) = 1).


Ejercicio 3 (Temas 1 y 4 — Vectores/registros, clases y herencia)

Enunciado

Defina los conceptos de vector y registro, poniendo como ejemplo cómo se representaría
un préstamo de ambas formas. A continuación, defina el concepto de herencia en un lenguaje
orientado a objetos y diseñe en PHP una jerarquía de clases para los usuarios de la biblioteca,
de manera que exista una clase base Usuario y tres subclases (Estudiante, PDI, PAS),
cada una con un método que devuelva el número máximo de días de préstamo permitidos (7, 30 y
15 días respectivamente). Justifique la elección de estas clases en relación con el modelo de
datos del enunciado.

Desarrollo y explicación

a) Vector vs. registro

$isbnsPrestados = ["978-3-16-148410-0", "978-1-23-456789-7", "978-0-30-680261-3"];
echo $isbnsPrestados[0]; // "978-3-16-148410-0"
$prestamo = [
    "dni"                     => "12345678A",
    "isbn"                    => "978-3-16-148410-0",
    "fechaPrestamo"           => "2025-05-01",
    "fechaDevolucionPrevista" => "2025-05-08",
    "estado"                  => "activo",
];
echo $prestamo["estado"]; // "activo"

En PHP, un array asociativo (clave => valor) se usa habitualmente para representar
registros, aunque la forma "más formal" de representar un registro en programación orientada
a objetos es precisamente una clase, que es lo que veremos a continuación.

b) Herencia

La herencia es un mecanismo de la programación orientada a objetos por el cual una clase
(llamada subclase o clase hija) puede reutilizar los atributos y métodos de otra clase
(llamada superclase o clase padre), y además añadir o redefinir (sobrescribir) los
suyos propios.

Ventajas:

c) Código PHP

<?php

// Clase "abstracta": no se pueden crear objetos directamente de Usuario,
// solo de sus subclases. Sirve para definir lo que TODO usuario tiene en común.
abstract class Usuario {

    protected string $dni;
    protected string $nrp;
    protected string $nombre;
    protected string $apellidos;

    public function __construct(string $dni, string $nrp, string $nombre, string $apellidos) {
        $this->dni        = $dni;
        $this->nrp        = $nrp;
        $this->nombre     = $nombre;
        $this->apellidos  = $apellidos;
    }

    public function getNombreCompleto(): string {
        return $this->nombre . " " . $this->apellidos;
    }

    // Método "abstracto": no tiene cuerpo aquí. OBLIGA a cada subclase
    // a implementarlo con su propia lógica.
    abstract public function diasMaximosPrestamo(): int;
}

class Estudiante extends Usuario {
    public function diasMaximosPrestamo(): int {
        return 7;
    }
}

class PDI extends Usuario {
    public function diasMaximosPrestamo(): int {
        return 30;
    }
}

class PAS extends Usuario {
    public function diasMaximosPrestamo(): int {
        return 15;
    }
}

// --- Programa principal ---
$ana = new Estudiante("12345678A", "EST001", "Ana", "García");
$luis = new PDI("87654321B", "PDI045", "Luis", "Martínez");

echo $ana->getNombreCompleto() . " puede tener un libro " . $ana->diasMaximosPrestamo() . " días\n";
echo $luis->getNombreCompleto() . " puede tener un libro " . $luis->diasMaximosPrestamo() . " días\n";

Explicación línea por línea:

d) Justificación de las clases elegidas

El modelo de datos incluye el campo TipoUsuario con tres valores posibles
(Estudiante, PDI, PAS). Como la regla de negocio (los días máximos de préstamo) depende
precisamente de ese campo
, tiene sentido modelarlo como una jerarquía de clases: una
superclase Usuario con los datos comunes (DNI, NRP, nombre, apellidos, presentes en la
entidad Persona/Usuario del enunciado) y tres subclases que representan cada valor posible
de TipoUsuario, cada una con su propia regla. Así, si en el futuro cambia la duración del
préstamo para el PAS, solo se modifica esa clase, sin tocar el resto del programa.


Ejercicio 4 (Tema 2 — Arquitectura cliente/servidor multicapa)

Enunciado

Describa la arquitectura cliente/servidor multicapa que utilizaría para implementar esta
aplicación de gestión de préstamos, indicando los componentes de cada capa y cómo se
comunican entre sí. Apoye su respuesta con un diagrama y un ejemplo del flujo completo de una
operación (por ejemplo, "solicitar un préstamo").

Desarrollo y explicación

La arquitectura cliente/servidor separa el sistema en dos partes:

En aplicaciones reales, el servidor casi nunca es "una sola cosa": se organiza en
capas (arquitectura multicapa o N-capas), normalmente tres:

  1. Capa de presentación: es la interfaz que ve el usuario (páginas HTML, formularios,
    componentes JavaScript). Su única responsabilidad es mostrar datos y recoger las acciones
    del usuario.
  2. Capa de negocio (o de aplicación/lógica): contiene las reglas del sistema: por
    ejemplo, "un estudiante solo puede tener un libro 7 días", "no se puede prestar un libro sin
    ejemplares disponibles". Aquí viven las clases como Usuario, Prestamo, etc.
  3. Capa de datos (o de persistencia): se encarga de guardar y recuperar la información de
    forma permanente, normalmente en una base de datos.

La separación en capas tiene ventajas: cada capa se puede modificar, probar o sustituir por
separado
(por ejemplo, cambiar de MySQL a PostgreSQL solo afecta a la capa de datos), y
varios clientes distintos (web, app móvil) pueden reutilizar la misma capa de negocio y de
datos.

Diagrama:

graph LR
    A["Cliente<br/>(Navegador / App móvil)<br/>HTML, CSS, JavaScript"]
    B["Capa de presentación<br/>(Servidor web / API REST)"]
    C["Capa de negocio<br/>(Clases PHP/Java: Usuario, Prestamo, Libro...)"]
    D["Capa de datos<br/>(Base de datos: MySQL/PostgreSQL)"]

    A -- "1. Petición HTTP (JSON)" --> B
    B -- "2. Llama a métodos" --> C
    C -- "3. Consultas SQL / ORM" --> D
    D -- "4. Resultados" --> C
    C -- "5. Resultado procesado" --> B
    B -- "6. Respuesta HTTP (JSON)" --> A

Ejemplo de flujo completo — "Solicitar un préstamo":

  1. El usuario rellena un formulario web (capa de presentación, en el navegador) con su DNI y el
    ISBN del libro, y pulsa "Enviar".
  2. El navegador envía una petición HTTP (por ejemplo, POST /api/prestamos) al servidor, con
    esos datos en formato JSON.
  3. El servidor recibe la petición en la capa de presentación/API, y la pasa a la capa de
    negocio
    .
  4. La capa de negocio aplica las reglas: comprueba que el libro tiene ejemplares
    disponibles, calcula la fecha de devolución prevista según el tipo de usuario (Estudiante,
    PDI o PAS, usando la jerarquía de clases del Ejercicio 3), etc.
  5. Si todo es correcto, la capa de negocio pide a la capa de datos que inserte el nuevo
    registro en la tabla Prestamo y reduzca en uno el campo EjemplaresDisponibles del libro.
  6. La capa de datos ejecuta las instrucciones SQL correspondientes contra la base de datos.
  7. El resultado (éxito o error) vuelve hacia arriba por las capas, hasta llegar al navegador,
    que muestra al usuario un mensaje de confirmación.

Ejercicio 5 (Tema 2 — Git, metodologías ágiles, pruebas y construcción de software)

Enunciado

El equipo de la unidad de informática va a desarrollar esta aplicación utilizando Git como
sistema de control de versiones y una metodología ágil. Explique: a) qué metodología ágil
aplicaría y sus características principales; b) el flujo de trabajo básico con Git que seguiría
el equipo para añadir la funcionalidad "renovar préstamo"; c) qué tipos de pruebas realizaría
antes de publicar esa funcionalidad, incluyendo un ejemplo de prueba unitaria en PHP con
PHPUnit para el método diasMaximosPrestamo() de la clase Estudiante del Ejercicio 3.

Desarrollo y explicación

a) Metodología ágil: Scrum

Scrum es la metodología ágil más utilizada. Sus características principales:

Frente a metodologías "en cascada" (donde todo se planifica al principio y se entrega al
final), Scrum permite adaptarse a cambios y obtener valor de forma incremental.

b) Flujo de trabajo con Git

Git permite que varias personas trabajen sobre el mismo proyecto sin pisarse el código, gracias
a las ramas (branches). El flujo típico para añadir "renovar préstamo" sería:

  1. Obtener la última versión del código compartido:

    git checkout main
    git pull origin main
    

    checkout cambia a la rama indicada; pull descarga y aplica los últimos cambios que haya
    en el repositorio remoto (origin).

  2. Crear una rama nueva para esta funcionalidad (así no se modifica directamente main):

    git checkout -b feature/renovar-prestamo
    

    -b crea la rama y se cambia a ella en un solo paso. Trabajar en una rama propia permite
    desarrollar sin afectar al código que otros compañeros están usando.

  3. Hacer los cambios en el código (añadir el método renovarPrestamo(), su test, etc.) y
    guardar el progreso:

    git add .
    git commit -m "Añade funcionalidad para renovar préstamos"
    

    add . marca todos los archivos modificados para que formen parte del próximo "guardado".
    commit crea ese "guardado" (una fotografía del proyecto en ese momento) con un mensaje
    descriptivo.

  4. Subir la rama al repositorio remoto (por ejemplo, GitHub/GitLab):

    git push origin feature/renovar-prestamo
    

  5. Abrir un Pull Request (o Merge Request): una solicitud para que estos cambios se
    incorporen a main. Otros miembros del equipo revisan el código (code review),
    pueden comentar o pedir cambios.

  6. Fusionar (merge) la rama en main una vez aprobada, normalmente tras comprobar que
    pasan las pruebas automáticas:

    git checkout main
    git merge feature/renovar-prestamo
    

    merge combina los cambios de la rama feature/renovar-prestamo dentro de main.

c) Pruebas de software

Antes de publicar una nueva funcionalidad conviene realizar varios niveles de pruebas:

Ejemplo de prueba unitaria en PHP con PHPUnit (la librería de pruebas más usada en
PHP), para el método diasMaximosPrestamo() de la clase Estudiante:

<?php
use PHPUnit\Framework\TestCase;

class EstudianteTest extends TestCase {

    public function testDiasMaximosPrestamoEsSiete(): void {
        // 1. Preparamos el objeto a probar
        $estudiante = new Estudiante("12345678A", "EST001", "Ana", "García");

        // 2. Llamamos al método que queremos comprobar
        $resultado = $estudiante->diasMaximosPrestamo();

        // 3. Comprobamos que el resultado es el esperado
        $this->assertEquals(7, $resultado);
    }
}

Explicación:

Estas pruebas se ejecutarían automáticamente, por ejemplo, cada vez que se hace git push,
mediante un sistema de integración continua (CI), que forma parte del proceso de
construcción de software: además de ejecutar pruebas, suele compilar el código (si aplica),
analizar su calidad y, si todo es correcto, generar la versión que se desplegará.


Ejercicio 6 (Tema 3 — HTML y XML)

Enunciado

Diseñe una página HTML con un formulario que permita a un usuario solicitar el préstamo de un
libro, indicando su DNI y el ISBN del libro deseado. Explique brevemente la función de cada
etiqueta utilizada. Además, represente en formato XML la información de un libro del catálogo
(ISBN, título, autor y ejemplares disponibles).

Desarrollo y explicación

a) Formulario HTML

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Solicitud de préstamo</title>
</head>
<body>
    <h1>Solicitar préstamo de libro</h1>

    <form action="/api/prestamos" method="POST">
        <label for="dni">DNI del usuario:</label>
        <input type="text" id="dni" name="dni" required>
        <br>

        <label for="isbn">ISBN del libro:</label>
        <input type="text" id="isbn" name="isbn" required>
        <br>

        <button type="submit">Solicitar préstamo</button>
    </form>
</body>
</html>

Explicación de cada etiqueta:

b) Representación XML de un libro

<?xml version="1.0" encoding="UTF-8"?>
<libro>
    <isbn>978-3-16-148410-0</isbn>
    <titulo>Estructuras de Datos y Algoritmos</titulo>
    <autor>Juan Pérez</autor>
    <ejemplaresDisponibles>3</ejemplaresDisponibles>
</libro>

Explicación:


Ejercicio 7 (Tema 4 — Implementación de clases en PHP/Java)

Enunciado

Implemente, en Java, la clase Prestamo con los atributos del modelo de datos
(dni, isbn, fechaPrestamo, fechaDevolucionPrevista, fechaDevolucionReal, estado) y
los métodos calcularDiasRetraso() y estaVencido(). Codifique además un programa principal
que cree dos objetos Prestamo (uno vencido y otro no) y muestre por consola, para cada uno,
si está vencido y cuántos días de retraso tiene.

Desarrollo y explicación

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

public class Prestamo {

    // --- Atributos (datos que tiene cada objeto Prestamo) ---
    private String dni;
    private String isbn;
    private LocalDate fechaPrestamo;
    private LocalDate fechaDevolucionPrevista;
    private LocalDate fechaDevolucionReal; // puede quedar sin asignar (null) si no se ha devuelto
    private String estado;

    // --- Constructor: se ejecuta al crear un objeto con "new" ---
    public Prestamo(String dni, String isbn, LocalDate fechaPrestamo,
                     LocalDate fechaDevolucionPrevista, String estado) {
        this.dni = dni;
        this.isbn = isbn;
        this.fechaPrestamo = fechaPrestamo;
        this.fechaDevolucionPrevista = fechaDevolucionPrevista;
        this.fechaDevolucionReal = null;
        this.estado = estado;
    }

    // --- Métodos (acciones que puede hacer un objeto Prestamo) ---

    public long calcularDiasRetraso() {
        LocalDate hoy = LocalDate.now();

        // Si todavía no se ha devuelto Y la fecha de hoy es posterior a la prevista...
        if (fechaDevolucionReal == null && hoy.isAfter(fechaDevolucionPrevista)) {
            // ChronoUnit.DAYS.between calcula la diferencia en días entre dos fechas
            return ChronoUnit.DAYS.between(fechaDevolucionPrevista, hoy);
        }
        return 0;
    }

    public boolean estaVencido() {
        return calcularDiasRetraso() > 0;
    }

    // --- getters (métodos para leer los atributos desde fuera de la clase) ---
    public String getDni() { return dni; }
    public String getIsbn() { return isbn; }

    // --- Programa principal ---
    public static void main(String[] args) {

        Prestamo p1 = new Prestamo("111", "AAA",
                LocalDate.of(2025, 5, 1), LocalDate.of(2025, 5, 8), "activo");

        Prestamo p2 = new Prestamo("222", "BBB",
                LocalDate.of(2026, 5, 1), LocalDate.of(2026, 5, 8), "activo");

        System.out.println("Préstamo 1 -> vencido: " + p1.estaVencido()
                + ", días de retraso: " + p1.calcularDiasRetraso());

        System.out.println("Préstamo 2 -> vencido: " + p2.estaVencido()
                + ", días de retraso: " + p2.calcularDiasRetraso());
    }
}

Explicación línea por línea:


Ejercicio 8 (Tema 5 — Acceso a bases de datos: JDBC y ORM)

Enunciado

a) Explique cómo conectaría la aplicación Java con una base de datos MySQL mediante JDBC
para insertar un nuevo préstamo, mostrando el código correspondiente.
b) Explique qué es un ORM y muestre cómo se definiría la clase Prestamo como entidad
JPA/Hibernate, incluyendo las anotaciones necesarias para mapear la tabla prestamo de la
base de datos.

Desarrollo y explicación

a) JDBC (Java Database Connectivity)

JDBC es la forma "clásica" de Java para conectarse a bases de datos relacionales: se abre una
conexión, se prepara una instrucción SQL y se ejecuta.

import java.sql.*;
import java.time.LocalDate;

public class PrestamoDAO {

    public void insertarPrestamo(Prestamo p) {

        String url     = "jdbc:mysql://localhost:3306/biblioteca";
        String usuario = "root";
        String clave   = "password";

        String sql = "INSERT INTO prestamo "
                    + "(dni, isbn, fecha_prestamo, fecha_devolucion_prevista, estado) "
                    + "VALUES (?, ?, ?, ?, ?)";

        // try-with-resources: cierra automáticamente la conexión al terminar
        try (Connection con = DriverManager.getConnection(url, usuario, clave);
             PreparedStatement stmt = con.prepareStatement(sql)) {

            stmt.setString(1, p.getDni());
            stmt.setString(2, p.getIsbn());
            stmt.setDate(3, java.sql.Date.valueOf(p.getFechaPrestamo()));
            stmt.setDate(4, java.sql.Date.valueOf(p.getFechaDevolucionPrevista()));
            stmt.setString(5, p.getEstado());

            stmt.executeUpdate(); // ejecuta el INSERT

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

Explicación:

b) ORM (Object-Relational Mapping)

Un ORM es una herramienta que traduce automáticamente entre objetos de un programa y
filas de tablas de una base de datos
, de forma que el programador trabaja con objetos
normales (new Prestamo(...), prestamo.getEstado()) y el ORM genera el SQL necesario por
detrás. En Java el más usado es Hibernate (a través del estándar JPA); en PHP, Doctrine.

Ventajas frente a escribir SQL a mano (JDBC/PDO directo):

La clase Prestamo como entidad JPA/Hibernate:

import javax.persistence.*;
import java.time.LocalDate;

@Entity
@Table(name = "prestamo")
public class Prestamo {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "dni")
    private String dni;

    @Column(name = "isbn")
    private String isbn;

    @Column(name = "fecha_prestamo")
    private LocalDate fechaPrestamo;

    @Column(name = "fecha_devolucion_prevista")
    private LocalDate fechaDevolucionPrevista;

    @Column(name = "fecha_devolucion_real")
    private LocalDate fechaDevolucionReal;

    @Column(name = "estado")
    private String estado;

    // Constructor vacío (obligatorio para Hibernate), constructores, getters y setters...
}

Explicación de cada anotación (las anotaciones, que empiezan por @, son "etiquetas" que
añaden información extra a la clase o sus atributos, usadas por Hibernate para saber cómo
mapear esta clase a la base de datos):

Con esto definido, en vez de escribir un INSERT a mano como en el apartado a), bastaría con
algo como:

entityManager.persist(nuevoPrestamo);

y Hibernate generaría automáticamente el INSERT INTO prestamo (...) VALUES (...)
correspondiente.


Ejercicio 9 (Tema 6 — API RESTful y JSON)

Enunciado

Diseñe una API RESTful para la gestión de préstamos que permita: 1) consultar todos los
préstamos de un usuario; 2) crear un nuevo préstamo; 3) marcar un préstamo como devuelto.
a) Defina los endpoints (URL + verbo HTTP) para cada operación. b) Implemente en PHP el
endpoint para consultar los préstamos de un usuario, devolviendo la respuesta en formato JSON.
c) Indique la estructura JSON de la respuesta.

Desarrollo y explicación

a) Diseño de los endpoints

Una API RESTful organiza las operaciones alrededor de recursos (sustantivos, como
"usuarios" o "préstamos"), identificados mediante URLs, y usa los verbos HTTP para indicar
qué acción se realiza sobre ese recurso:

Operación Verbo HTTP URL (endpoint) Significado
Consultar préstamos de un usuario GET /api/usuarios/{dni}/prestamos GET = leer, sin modificar nada
Crear un nuevo préstamo POST /api/prestamos POST = crear un nuevo recurso
Marcar un préstamo como devuelto PUT /api/prestamos/{id}/devolver PUT/PATCH = modificar un recurso existente

{dni} e {id} son parámetros en la URL: se sustituyen por valores reales, por ejemplo
/api/usuarios/12345678A/prestamos.

b) Implementación en PHP del endpoint de consulta

<?php
// Indicamos que la respuesta será JSON
header("Content-Type: application/json");

// Recogemos el DNI que viene en la URL, p.ej. /api/usuarios/12345678A/prestamos
$dni = $_GET['dni'];

// Conexión a la base de datos mediante PDO
$pdo = new PDO("mysql:host=localhost;dbname=biblioteca", "root", "password");

// Consulta preparada (con ":dni" como parámetro, evita inyección SQL)
$stmt = $pdo->prepare(
    "SELECT isbn, fecha_prestamo, fecha_devolucion_prevista, estado
     FROM prestamo
     WHERE dni = :dni"
);
$stmt->execute(['dni' => $dni]);

// Obtenemos todas las filas como un array de arrays asociativos
$prestamos = $stmt->fetchAll(PDO::FETCH_ASSOC);

// Convertimos el array PHP a texto JSON y lo enviamos como respuesta
echo json_encode($prestamos);

Explicación:

c) Estructura JSON de la respuesta

[
    {
        "isbn": "978-3-16-148410-0",
        "fecha_prestamo": "2025-05-01",
        "fecha_devolucion_prevista": "2025-05-08",
        "estado": "activo"
    },
    {
        "isbn": "978-1-23-456789-7",
        "fecha_prestamo": "2025-04-10",
        "fecha_devolucion_prevista": "2025-04-17",
        "estado": "devuelto"
    }
]

Explicación de la estructura JSON:


Ejercicio 10 (Tema 7 — JavaScript/TypeScript y frameworks SPA)

Enunciado

Escriba un fragmento de código JavaScript que, utilizando fetch y async/await, consuma el
endpoint GET /api/usuarios/{dni}/prestamos del ejercicio anterior y muestre en la página la
lista de préstamos obtenidos. Reescriba después la parte de obtención de datos en TypeScript,
definiendo el tipo de dato Prestamo. Explique además, de forma general, qué es un framework
SPA (Single Page Application) y cómo se estructuraría un componente sencillo (en Vue) para
mostrar esta misma lista.

Desarrollo y explicación

a) JavaScript con fetch y async/await

async function cargarPrestamos(dni) {
    try {
        // 1. Hacemos la petición a la API (equivalente al navegador visitando esa URL)
        const respuesta = await fetch(`/api/usuarios/${dni}/prestamos`);

        // 2. Comprobamos si la respuesta indica error (código HTTP 4xx o 5xx)
        if (!respuesta.ok) {
            throw new Error("Error al obtener los préstamos");
        }

        // 3. Convertimos el cuerpo de la respuesta (texto JSON) a un objeto/array de JavaScript
        const prestamos = await respuesta.json();

        // 4. Mostramos los datos en la página
        const lista = document.getElementById("listaPrestamos");
        lista.innerHTML = ""; // vaciamos la lista por si tenía contenido anterior

        prestamos.forEach(prestamo => {
            const item = document.createElement("li");
            item.textContent = `Libro: ${prestamo.isbn} - Estado: ${prestamo.estado}`;
            lista.appendChild(item);
        });

    } catch (error) {
        console.error(error);
    }
}

// Llamamos a la función, por ejemplo al cargar la página
cargarPrestamos("12345678A");

Explicación:

b) La misma lógica en TypeScript

TypeScript añade tipos de datos a JavaScript. Esto permite que el editor de código (y el
propio lenguaje, al compilar) detecten errores antes de ejecutar el programa: por ejemplo,
si intentamos acceder a prestamo.titulo cuando ese campo no existe, TypeScript avisaría.

// Definimos la "forma" que tendrá cada préstamo que llega de la API
interface Prestamo {
    isbn: string;
    fecha_prestamo: string;
    fecha_devolucion_prevista: string;
    estado: string;
}

async function obtenerPrestamos(dni: string): Promise<Prestamo[]> {
    const respuesta = await fetch(`/api/usuarios/${dni}/prestamos`);

    if (!respuesta.ok) {
        throw new Error("Error al obtener los préstamos");
    }

    const prestamos: Prestamo[] = await respuesta.json();
    return prestamos;
}

Explicación:

c) Frameworks SPA (Single Page Application)

Un framework SPA (como Angular, React o Vue) es una herramienta para construir
interfaces web donde, tras la carga inicial, la página no se recarga por completo al
navegar o al actualizar datos: el framework actualiza solo las partes necesarias del contenido.

Características comunes:

Ejemplo de un componente sencillo en Vue para mostrar la lista de préstamos:

<template>
  <ul>
    <li v-for="prestamo in prestamos" :key="prestamo.isbn">
      Libro: {{ prestamo.isbn }} - Estado: {{ prestamo.estado }}
    </li>
  </ul>
</template>

<script>
export default {
  data() {
    return {
      prestamos: [] // lista vacía al principio
    };
  },
  async mounted() {
    // mounted() se ejecuta automáticamente cuando el componente aparece en pantalla
    const respuesta = await fetch("/api/usuarios/12345678A/prestamos");
    this.prestamos = await respuesta.json();
  }
};
</script>

Explicación:


(Fin del Cuaderno 1. Continúa en el Cuaderno 2: Sistema de Cita Previa para trámites
administrativos.)