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.
| 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 |
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 |
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.
entero (int): números sin decimales (1, 25, -3).decimal/flotante (float/double): números con decimales (3.14).cadena de texto (string): texto, siempre entre comillas ("Hola").booleano (bool): solo puede valer verdadero (true) o falso (false).fecha/hora (date/datetime): para fechas como "2025-05-01".+ - * / % (suma, resta, multiplicación, división, resto).== != > < >= <= (devuelven verdadero/falso).&& (Y), || (O), ! (NO). Combinan condiciones booleanas.if): permite que el programa tome decisiones, ejecutando unfor, while, foreach): repite un bloque de código varias veces, normalmenteUsuario es lausuario1 (DNI "123", nombre "Ana") es un objeto./usuarios/123) y los verbos HTTP (GET, POST, PUT, DELETE) para"clave": valor y listas [ ], parecido a los registros y vectores explicados arriba.SELECT), insertar (INSERT), modificar (UPDATE) y borrarDELETE) esos datos.fetch se usa para hacer peticiones a una API.async/await para "esperar" el resultado sinLa 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):
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).
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:
$dni = "12345678A"; → el símbolo $ indica que es una variable en PHP. A la izquierda vanew DateTime("2025-05-01") → crea un "objeto" de tipo fecha a partir de un texto. No haceDateTime es un tipo de dato especial que sabe hacer cálculos con=== compara valor Y tipo. Es más estricto que ==, que solo compara el valor. Se usanull.&& es el operador lógico Y: la expresión completa solo es verdadera si ambas partes$hoy > $fechaDevolucionPrevista → en PHP, comparar dos objetos DateTime con > comparatrue o false (un booleano).? : es el operador ternario, una forma compacta de escribir:if ($hoy > $fechaDevolucionPrevista) {
$diasRetraso = $diferencia->days;
} else {
$diasRetraso = 0;
}
. (punto) en PHP es el operador de concatenación de cadenas: une dos textos en uno solo.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.
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:
$prestamos = [ [...], [...], [...] ]; es un array de registros. Cada elemento del array"dni", "isbn"...) en vez de solo una posición. Esto es exactamente lo que en otrosforeach ($prestamos as $prestamo) es un bucle: repite el bloque { ... } una vez por$prestamos, guardando en $prestamo el elemento actual en cada repetición.$prestamo["dni"] accede al campo dni de ese registro.if (...) es la instrucción condicional: el contenido entre { } solo se ejecuta si la{$prestamo['dni']} dentro de una cadena con comillas dobles es una forma de insertar elb) 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:
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:
function fechaMasAntigua(array $prestamos, int $indice = 0, ?DateTime $minima = null):$indice = 0 y $minima = null son valoresfechaMasAntigua($prestamos) funciona con solo un argumento).count($prestamos) devuelve cuántos elementos tiene el array.$indice >= count($prestamos) es el caso base: cuando el índice llega al final del$minima y la recursividad termina.return fechaMasAntigua($prestamos, $indice + 1, $minima); es la llamada recursiva: la$indice + 1 (avanzando una posición), acercándose?DateTime significa "puede ser un DateTime o null" (tipo "anulable").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).
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.
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:
Usuario.Estudiante, un PDI o unPAS simplemente como un Usuario, y al llamar a diasMaximosPrestamo() cada uno responderá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:
abstract class Usuario { ... } → declara una clase llamada Usuario. La palabraabstract indica que es una clase "incompleta a propósito": sirve de plantilla común, peroUsuario directamente (¡todo usuario real es Estudiante, PDI oprotected string $dni; → declara un atributo llamado $dni, de tipo string. Laprotected significa que solo esta clase y sus subclases pueden accederpublic function __construct(...) → es el constructor. Se ejecuta automáticamentenew. Recibe los datos iniciales como parámetros.$this->dni = $dni; → $this representa "el objeto que se está creando ahora mismo".abstract public function diasMaximosPrestamo(): int; → declara que toda subclase deUsuario debe tener un método diasMaximosPrestamo() que devuelva un entero (int),class Estudiante extends Usuario { ... } → extends es la palabra clave de la herencia:Estudiante hereda todo lo de Usuario (atributos, constructor, getNombreCompleto()) ydiasMaximosPrestamo().new Estudiante("12345678A", "EST001", "Ana", "García") → crea un objeto (instancia) deEstudiante, llamando a su constructor (heredado de Usuario) con esos cuatro$ana->getNombreCompleto() → el operador -> se usa para acceder a un método o atributoUsuario.$ana->diasMaximosPrestamo() → aunque el método está declarado en Usuario, cada objetoEstudiante devuelve 7, PDI devuelved) 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.
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").
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:
Usuario, Prestamo, etc.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":
POST /api/prestamos) al servidor, conPrestamo y reduzca en uno el campo EjemplaresDisponibles del libro.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.
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:
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 hayaorigin).
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 permiteHacer 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 mensajeSubir la rama al repositorio remoto (por ejemplo, GitHub/GitLab):
git push origin feature/renovar-prestamo
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.
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:
class EstudianteTest extends TestCase → es una clase de prueba. extends TestCase indicapublic function testDiasMaximosPrestamoEsSiete(): void → cada método cuyo nombre empiezatest es una prueba que PHPUnit ejecutará automáticamente. void indica que no$this->assertEquals(7, $resultado); → un assert ("afirmación") comprueba que dos$resultado no es 7, la prueba falla y PHPUnit lo indicará enEstas 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á.
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).
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:
<!DOCTYPE html> → indica al navegador que el documento usa HTML5 (la versión actual de<html lang="es"> ... </html> → es la etiqueta "raíz", que envuelve todo el documento.lang="es" indica que el contenido está en español (ayuda, por ejemplo, a lectores de<head> ... </head> → contiene información sobre la página (metadatos), que no se<meta charset="UTF-8"> → indica la codificación de caracteres, necesaria para que se<title>...</title> → el texto que aparece en la pestaña del navegador.<body> ... </body> → contiene todo el contenido visible de la página.<h1>...</h1> → un título de primer nivel (el más importante de la página).<form action="/api/prestamos" method="POST"> ... </form> → define un formulario.action="/api/prestamos" indica a qué dirección se enviarán los datos al pulsar elmethod="POST" indica que los datos se enviarán mediante el verbo HTTP POST (adecuado<label for="dni">...</label> → una etiqueta de texto asociada a un campo del formulario.for="dni" la relaciona con el campo cuyo id es "dni": si el usuario hace<input type="text" id="dni" name="dni" required> → un campo de entrada de texto.type="text" → es un campo de texto libre.id="dni" → identificador único del campo (lo usa <label for="dni"> para enlazar).name="dni" → es el nombre con el que este dato se enviará al servidor (por ejemplo,dni=12345678A).required → el navegador no permitirá enviar el formulario si este campo está vacío.<br> → un salto de línea simple.<button type="submit">...</button> → un botón que, al pulsarlo, envía el formulario<input type="submit">).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:
<?xml version="1.0" encoding="UTF-8"?> → es la declaración XML, indica la versión del<libro> ... </libro> → es el elemento raíz: todo documento XML debe tener exactamente<isbn>...</isbn>, <titulo>...</titulo>, etc. → son elementos "hijos" del elemento raíz,Libro del modelo de datos. A diferencia de<isbn>) debe cerrarse (</isbn>): a esto se le llama unImplemente, 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.
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:
import java.time.LocalDate; y import java.time.temporal.ChronoUnit; → Java organiza suimport permite usar clases de utilidad ya existentes para trabajarLocalDate) y calcular diferencias entre ellas (ChronoUnit), sin tener quepublic class Prestamo { ... } → declara la clase. En Java, el nombre del archivo debePrestamo.java).private String dni; → declara un atributo. private significa que solo se puede accederdni directamente, solo a través de métodos que la clase ofrezca).public Prestamo(...) → es el constructor: tiene el mismo nombre que la clase y nothis.dni = dni; → this se refiere "al objeto actual". Como el parámetro también se llamadni, this.dni (el atributo) se distingue de dni (el parámetro).public long calcularDiasRetraso() → un método (función dentro de una clase) quelong).LocalDate.now() → obtiene la fecha actual del sistema.hoy.isAfter(fechaDevolucionPrevista) → método que devuelve true si hoy es una fechafechaDevolucionPrevista.ChronoUnit.DAYS.between(fechaDevolucionPrevista, hoy) → calcula cuántos días hay entre dospublic boolean estaVencido() → este método reutiliza calcularDiasRetraso(): en vez depublic static void main(String[] args) → es el punto de entrada del programa: cuando sePrestamo.java, Java empieza a ejecutar el código que está dentro de estestatic significa que este método pertenece a la clase en general, no a un objetoPrestamo todavía).LocalDate.of(2025, 5, 1) → crea una fecha concreta (1 de mayo de 2025).System.out.println(...) → muestra un texto por la consola (la "pantalla" del programa),+ aquí concatena textos y valores (igual que .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.
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:
DAO significa Data Access Object: es un patrón habitual donde se crea una clasePrestamo).DriverManager.getConnection(url, usuario, clave) → abre una conexión con la base deurl (en este caso, una base de datos MySQL llamada biblioteca que estálocalhost, puerto 3306, el puerto por defecto de MySQL).con.prepareStatement(sql) → prepara la instrucción SQL. Los signos ? son parámetrosstmt.setString(1, p.getDni()) → asigna el valor del primer ? (posición 1). ExistensetString, setDate, setInt, etc.stmt.executeUpdate() → ejecuta la instrucción. Se usa executeUpdate para INSERT,UPDATE y DELETE (instrucciones que modifican datos); para consultas que leenSELECT) se usaría executeQuery, que devuelve un ResultSet con los resultados.try (...) { ... } catch (SQLException e) { ... } → un bloque try ejecuta código queSQLException, se "captura" en el bloque catch en vez de detener todo el programatry (Connection con = ...) se llama try-with-resources: Javab) 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):
Prestamo tiene unUsuario y un Libro), y el ORM se encarga de traducirlo a claves foráneas y JOINs.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):
@Entity → indica que esta clase representa una entidad persistente, es decir, que sus@Table(name = "prestamo") → indica que los objetos de esta clase corresponden a filas deprestamo. Si se omitiera, Hibernate usaría por defecto el nombre de la clase.@Id → marca el atributo id como la clave primaria de la tabla (el identificador único@GeneratedValue(strategy = GenerationType.IDENTITY) → indica que el valor del id lo genera@Column(name = "dni") → indica que el atributo dni corresponde a la columna dni de laCon 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.
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.
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:
header("Content-Type: application/json"); → añade una "cabecera HTTP" a la respuesta,$_GET['dni'] → en PHP, $_GET es un array especial que contiene los parámetros que llegannew PDO("mysql:host=localhost;dbname=biblioteca", "root", "password") → PDO (PHP Data$pdo->prepare(...) y $stmt->execute(['dni' => $dni]) → de nuevo, una consulta:dni es un marcador que se sustituye de forma segura por el valor de $dni.$stmt->fetchAll(PDO::FETCH_ASSOC) → recupera todas las filas del resultado, cada una["isbn" => ..., "estado" => ...]), igual que los registros deljson_encode($prestamos) → convierte el array PHP a una cadena de texto en formato JSON,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:
[ ] representan una lista (equivalente a un vector/array): aquí, la lista{ }, que representan un objeto (equivalente"clave": valor. Las claves siempre van entre comillas dobles.true/false),null, otros objetos { } o listas [ ].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.
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:
async function cargarPrestamos(dni) { ... } → la palabra async indica que esta funciónawait dentro de ella.fetch(\/api/usuarios/${dni}/prestamos`)→fetches la función estándar de JavaScript
para hacer peticiones HTTP. La barra invertida con comillas \...` es una plantilla de${ } (similar a {$variable} enawait → "espera" a que la operación (fetch, .json()) termine antes de continuar con laawait, fetchrespuesta.ok → es true si el servidor respondió con un código de éxito (200-299), yfalse si hubo un error (por ejemplo, 404 o 500).respuesta.json() → interpreta el cuerpo de la respuesta como JSON y lo convierte en unjson_encode en PHP).document.getElementById("listaPrestamos") → busca en la página HTML el elemento cuyoid="listaPrestamos" (por ejemplo, una etiqueta <ul id="listaPrestamos"></ul>).prestamos.forEach(prestamo => { ... }) → recorre el array prestamos, ejecutando elforeach de PHP).document.createElement("li") y lista.appendChild(item) → crean un nuevo elemento HTML<li>, un elemento de lista) y lo añaden dentro de listaPrestamos.try { ... } catch (error) { ... } → igual que en PHP/Java, captura errores que puedanb) 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:
interface Prestamo { ... } → define un tipo de dato propio llamado Prestamo,string en este caso). Es eldni: string (en los parámetros de la función) → indica que el parámetro dni debe serobtenerPrestamos(123) (un número), TypeScript marcaría un error antes de ejecutar nada.Promise<Prestamo[]> → indica que la función devuelve, de forma asíncrona (Promise), unPrestamo.const prestamos: Prestamo[] = await respuesta.json(); → le decimos a TypeScript que confíePrestamo. A partir de aquí, siprestamos[0].estado, el editor sabe que estado existe y es un string.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:
document.getElementById, etc.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:
<template> ... </template> → define el HTML del componente.v-for="prestamo in prestamos" → es una directiva de Vue: repite el elemento <li> unaprestamos (es el equivalente, en la plantilla, alforEach/foreach que escribíamos a mano en los apartados anteriores).:key="prestamo.isbn" → ayuda a Vue a identificar cada elemento de la lista de forma única,{{ prestamo.isbn }} → muestra el valor de prestamo.isbn directamente en el HTML.<script> export default { ... } </script> → define la lógica del componente.data() { return { prestamos: [] }; } → declara los datos del componente. Cuandothis.prestamos cambie, Vue actualizará automáticamente el <ul> en pantalla, sin que<li> a mano).async mounted() { ... } → es un "gancho de ciclo de vida" (lifecycle hook): código que(Fin del Cuaderno 1. Continúa en el Cuaderno 2: Sistema de Cita Previa para trámites
administrativos.)