Generado con IA · Supuesto práctico

Modelado y consulta de la base de datos de préstamo de portátiles del centro

Caso único con preguntas Informática Comunidad Valenciana
Descargar:
Supuesto práctico generado por la IA de OposicionesIA, con su orientación y solución modelo más abajo. Dentro de la plataforma, además, puedes resolverlo y recibir una corrección con rúbrica y nota.

Modelado y consulta de la base de datos de préstamo de portátiles del centro

Contexto

El IES «Blasco Ibáñez» (València) gestiona el préstamo de portátiles del programa de digitalización. Necesita una base de datos en MariaDB/MySQL que registre el alumnado, el parque de portátiles, los préstamos y las incidencias técnicas.

Requisitos recogidos del centro:

  • De cada alumno/a interesa su NIA (identificador único de la GVA), nombre, apellidos y grupo (p. ej. «2n DAM A»).
  • De cada portátil interesa su número de serie (único), marca, modelo, fecha de compra y estado (disponible, prestado, baja).
  • Un préstamo asocia un alumno con un portátil en una fecha, con fecha de devolución prevista y, cuando se devuelve, fecha de devolución real. Un mismo alumno puede tener varios préstamos a lo largo del curso, y un mismo portátil se presta muchas veces (nunca a dos personas a la vez).
  • Cada portátil puede acumular varias incidencias (descripción y fecha); una incidencia pertenece a un único portátil.

Cuestiones

1. Diseño conceptual. Dibuja (o describe con precisión) el diagrama Entidad-Relación extendido: entidades, atributos, identificadores, y las relaciones con sus cardinalidades mínimas y máximas.

2. Modelo relacional (3FN). Transforma el diagrama anterior en el modelo relacional normalizado hasta 3ª Forma Normal. Indica para cada tabla sus columnas, la clave primaria y las claves ajenas (con las políticas de borrado/actualización que consideres adecuadas). Justifica que no hay dependencias transitivas.

3. Consultas SQL (dificultad creciente). Escribe las siguientes sentencias sobre tu modelo:

  1. Listado de los portátiles actualmente en estado disponible, ordenados por marca y modelo.
  2. Número de préstamos realizados por cada grupo de alumnado, mostrando solo los grupos con más de 10 préstamos (usa JOIN + GROUP BY + HAVING).
  3. Préstamos vencidos: aquellos cuya fecha de devolución prevista ya pasó y que aún no se han devuelto (fecha_devolucion_real nula), mostrando NIA, nombre del alumno y número de serie del portátil.
  4. Portátiles que nunca han tenido ninguna incidencia (resuélvelo con una subconsulta o con LEFT JOIN ... IS NULL).

4. Integridad con un disparador (TRIGGER). Crea un trigger que, al insertar un nuevo préstamo, impida prestar un portátil que no esté en estado disponible: debe abortar la operación con un mensaje de error (SIGNAL SQLSTATE '45000') y, si el préstamo es válido, marcar el portátil como prestado. Explica brevemente en qué momento (BEFORE/AFTER) y por qué.

5. Mini-cuestión. ¿Qué problema de integridad podría aparecer si la columna estado del portátil se mantuviera «a mano» en lugar de mediante el trigger? Propón una alternativa basada en una vista que derive el estado a partir de los préstamos abiertos.

Orientación cómo resolverlo por tu cuenta

Este supuesto recorre el ciclo completo de diseño de bases de datos del módulo de «Bases de Datos» (1.º DAM/DAW): del modelo conceptual al físico, más SQL avanzado y programación de servidor. Aborda las cuestiones en orden, porque cada una depende de la anterior.

Cuestión 1 (E-R). Identifica primero las entidades fuertes a partir de los sustantivos del enunciado (alumno, portátil, préstamo, incidencia). Decide si «préstamo» es entidad o relación: como tiene atributos propios y un alumno puede repetir portátil en fechas distintas, conviene tratarla como entidad asociativa (o relación N:M con atributos). Marca el identificador de cada entidad (fíjate en cuáles son «naturales»: NIA, número de serie). Para las cardinalidades, razona min/max en ambos sentidos: ¿un portátil puede existir sin préstamos? ¿una incidencia sin portátil? Error típico: confundir cardinalidad máxima con participación mínima (total/parcial).

Cuestión 2 (paso a relacional + 3FN). Recuerda las reglas: cada entidad → tabla; relación N:M → tabla propia con FK a ambos lados. Define PK y FK, y elige políticas referenciales (ON DELETE / ON UPDATE) coherentes con la semántica (no se borra «en cascada» un alumno con historial). Para justificar la 3FN, demuestra que todo atributo no clave depende de la clave, de toda la clave y solo de la clave: busca dependencias transitivas (atención al grupo del alumno).

Cuestión 3 (SQL). Ve de menos a más: filtro simple con ORDER BY; agregación con JOIN + GROUP BY + HAVING (recuerda que HAVING filtra grupos, WHERE filas); fechas vencidas con comparación a CURDATE() y test de nulidad; y la antijunción con LEFT JOIN ... IS NULL o NOT EXISTS. Error típico: usar WHERE en lugar de HAVING para el conteo, o == en vez de IS NULL.

Cuestión 4 (TRIGGER). Piensa cuándo validar: antes de insertar, para poder abortar con SIGNAL SQLSTATE '45000'. Cuidado: actualizar el estado del portátil dentro del propio trigger BEFORE INSERT es legítimo (es otra tabla). Razona la atomicidad.

Cuestión 5. Relaciona el problema con la redundancia/desnormalización del estado (dato derivado duplicado → riesgo de inconsistencia) y propón una vista que calcule el estado en tiempo real.

Solución modelo respuesta completa — intenta resolverlo antes de mirar

Cuestión 1 — Diseño conceptual (Diagrama E-R extendido)

Entidades y atributos (subrayado = identificador):

  • ALUMNO( nia, nombre, apellidos, grupo )
  • PORTATIL( num_serie, marca, modelo, fecha_compra, estado )
  • PRESTAMO( id_prestamo, fecha_prestamo, fecha_dev_prevista, fecha_dev_real )
  • INCIDENCIA( id_incidencia, descripcion, fecha )

Decisión de modelado. El préstamo asocia un alumno con un portátil, pero un mismo alumno repite portátil y un mismo portátil se presta muchas veces en fechas distintas. Por tanto la relación alumno-portátil es N:M con atributos propios (fechas) y la modelamos como entidad asociativa PRESTAMO con identificador artificial id_prestamo (más cómodo que la clave compuesta natural nia+num_serie+fecha, que además fallaría si se permitieran dos préstamos el mismo día). La incidencia es una entidad débil por identificación respecto al portátil (o entidad fuerte con FK; se opta por id propio).

Relaciones y cardinalidades (mín, máx):

Relación Entidad A Entidad B Cardinalidad
realiza ALUMNO (1,1) PRESTAMO (0,N) Un préstamo es de un solo alumno; un alumno tiene 0..N préstamos
afecta_a PORTATIL (1,1) PRESTAMO (0,N) Un préstamo es de un solo portátil; un portátil tiene 0..N préstamos
padece PORTATIL (1,1) INCIDENCIA (0,N) Una incidencia pertenece a un solo portátil; un portátil tiene 0..N incidencias

Lectura de las participaciones: ALUMNO y PORTATIL tienen participación parcial en realiza/afecta_a (mínimo 0: pueden existir sin préstamos). PRESTAMO tiene participación total hacia ambos (1,1): no existe préstamo sin alumno ni sin portátil. INCIDENCIA es total hacia PORTATIL (1,1); PORTATIL es parcial (0,N).

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 400" font-family="ui-sans-serif, system-ui, Arial, sans-serif"> <!-- ALUMNO --> <rect x="20" y="90" width="180" height="100" rx="8" fill="#f8fafc" stroke="#334155" stroke-width="1.4"/> <rect x="20" y="90" width="180" height="24" rx="8" fill="#e2e8f0" stroke="#334155" stroke-width="1.4"/> <text x="110" y="107" text-anchor="middle" font-size="13" font-weight="bold" fill="#0f172a">ALUMNO</text> <text x="32" y="132" font-size="11" fill="#0f172a">nia</text> <line x1="32" y1="135" x2="50" y2="135" stroke="#0f172a" stroke-width="1"/> <text x="32" y="148" font-size="11" fill="#0f172a">nombre</text> <text x="32" y="164" font-size="11" fill="#0f172a">apellidos</text> <text x="32" y="180" font-size="11" fill="#0f172a">grupo</text> <!-- PRESTAMO --> <rect x="290" y="90" width="180" height="100" rx="8" fill="#f8fafc" stroke="#334155" stroke-width="1.4"/> <rect x="290" y="90" width="180" height="24" rx="8" fill="#e2e8f0" stroke="#334155" stroke-width="1.4"/> <text x="380" y="107" text-anchor="middle" font-size="13" font-weight="bold" fill="#0f172a">PRESTAMO</text> <text x="302" y="132" font-size="11" fill="#0f172a">id_prestamo</text> <line x1="302" y1="135" x2="364" y2="135" stroke="#0f172a" stroke-width="1"/> <text x="302" y="148" font-size="11" fill="#0f172a">fecha_prestamo</text> <text x="302" y="164" font-size="11" fill="#0f172a">fecha_dev_prevista</text> <text x="302" y="180" font-size="11" fill="#0f172a">fecha_dev_real</text> <!-- PORTATIL --> <rect x="560" y="70" width="185" height="116" rx="8" fill="#f8fafc" stroke="#334155" stroke-width="1.4"/> <rect x="560" y="70" width="185" height="24" rx="8" fill="#e2e8f0" stroke="#334155" stroke-width="1.4"/> <text x="652" y="87" text-anchor="middle" font-size="13" font-weight="bold" fill="#0f172a">PORTATIL</text> <text x="572" y="112" font-size="11" fill="#0f172a">num_serie</text> <line x1="572" y1="115" x2="627" y2="115" stroke="#0f172a" stroke-width="1"/> <text x="572" y="128" font-size="11" fill="#0f172a">marca</text> <text x="572" y="144" font-size="11" fill="#0f172a">modelo</text> <text x="572" y="160" font-size="11" fill="#0f172a">fecha_compra</text> <text x="572" y="176" font-size="11" fill="#0f172a">estado</text> <!-- INCIDENCIA --> <rect x="560" y="300" width="185" height="84" rx="8" fill="#f8fafc" stroke="#334155" stroke-width="1.4"/> <rect x="560" y="300" width="185" height="24" rx="8" fill="#e2e8f0" stroke="#334155" stroke-width="1.4"/> <text x="652" y="317" text-anchor="middle" font-size="13" font-weight="bold" fill="#0f172a">INCIDENCIA</text> <text x="572" y="342" font-size="11" fill="#0f172a">id_incidencia</text> <line x1="572" y1="345" x2="638" y2="345" stroke="#0f172a" stroke-width="1"/> <text x="572" y="358" font-size="11" fill="#0f172a">descripcion</text> <text x="572" y="374" font-size="11" fill="#0f172a">fecha</text> <!-- realiza: ALUMNO (1,1) - (0,N) PRESTAMO --> <line x1="200" y1="140" x2="210" y2="140" stroke="#334155" stroke-width="1.6"/> <line x1="280" y1="140" x2="290" y2="140" stroke="#334155" stroke-width="1.6"/> <polygon points="245,123 280,140 245,157 210,140" fill="#fffbeb" stroke="#d97706" stroke-width="1.4"/> <text x="245" y="143" text-anchor="middle" font-size="10" fill="#9a3412">realiza</text> <text x="213" y="134" font-size="10" fill="#64748b">(1,1)</text> <text x="277" y="134" text-anchor="end" font-size="10" fill="#64748b">(0,N)</text> <!-- afecta_a: PORTATIL (1,1) - (0,N) PRESTAMO --> <line x1="470" y1="135" x2="480" y2="135" stroke="#334155" stroke-width="1.6"/> <line x1="550" y1="135" x2="560" y2="135" stroke="#334155" stroke-width="1.6"/> <polygon points="515,118 550,135 515,152 480,135" fill="#fffbeb" stroke="#d97706" stroke-width="1.4"/> <text x="515" y="138" text-anchor="middle" font-size="10" fill="#9a3412">afecta_a</text> <text x="483" y="129" font-size="10" fill="#64748b">(0,N)</text> <text x="547" y="129" text-anchor="end" font-size="10" fill="#64748b">(1,1)</text> <!-- padece: PORTATIL (0,N) - (1,1) INCIDENCIA --> <line x1="652" y1="186" x2="652" y2="220" stroke="#334155" stroke-width="1.6"/> <line x1="652" y1="266" x2="652" y2="300" stroke="#334155" stroke-width="1.6"/> <polygon points="652,220 682,243 652,266 622,243" fill="#fffbeb" stroke="#d97706" stroke-width="1.4"/> <text x="652" y="246" text-anchor="middle" font-size="10" fill="#9a3412">padece</text> <text x="662" y="204" font-size="10" fill="#64748b">(0,N)</text> <text x="662" y="292" font-size="10" fill="#64748b">(1,1)</text> </svg>

Figura 1. Diagrama Entidad-Relación extendido: ALUMNO y PORTATIL con PRESTAMO como entidad asociativa (N:M con atributos) e INCIDENCIA colgando de PORTATIL; cada arco rotulado con su cardinalidad (mín, máx).


Cuestión 2 — Modelo relacional normalizado (3FN)

Reglas de transformación: cada entidad fuerte → una tabla; la entidad asociativa PRESTAMO → tabla con FK a ALUMNO y a PORTATIL; INCIDENCIA → tabla con FK a PORTATIL.

ALUMNO    ( nia (PK), nombre, apellidos, grupo )
PORTATIL  ( num_serie (PK), marca, modelo, fecha_compra, estado )
PRESTAMO  ( id_prestamo (PK), nia (FK→ALUMNO), num_serie (FK→PORTATIL),
            fecha_prestamo, fecha_dev_prevista, fecha_dev_real )
INCIDENCIA( id_incidencia (PK), num_serie (FK→PORTATIL), descripcion, fecha )
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 400" font-family="ui-sans-serif, system-ui, Arial, sans-serif"> <!-- ALUMNO --> <rect x="20" y="30" width="180" height="104" rx="6" fill="#ffffff" stroke="#334155" stroke-width="1.3"/> <rect x="20" y="30" width="180" height="24" fill="#e2e8f0"/> <text x="110" y="47" text-anchor="middle" font-size="12" font-weight="bold" fill="#0f172a">ALUMNO</text> <rect x="20" y="54" width="180" height="20" fill="#eef2ff"/> <text x="30" y="68" font-size="11" fill="#0f172a">nia</text> <rect x="168" y="57" width="24" height="14" rx="3" fill="#2563eb"/> <text x="180" y="67" text-anchor="middle" font-size="9" font-weight="bold" fill="#ffffff">PK</text> <text x="30" y="88" font-size="11" fill="#0f172a">nombre</text> <text x="30" y="108" font-size="11" fill="#0f172a">apellidos</text> <text x="30" y="128" font-size="11" fill="#0f172a">grupo</text> <line x1="20" y1="74" x2="200" y2="74" stroke="#e2e8f0" stroke-width="1"/> <!-- PORTATIL --> <rect x="540" y="20" width="200" height="124" rx="6" fill="#ffffff" stroke="#334155" stroke-width="1.3"/> <rect x="540" y="20" width="200" height="24" fill="#e2e8f0"/> <text x="640" y="37" text-anchor="middle" font-size="12" font-weight="bold" fill="#0f172a">PORTATIL</text> <rect x="540" y="44" width="200" height="20" fill="#eef2ff"/> <text x="550" y="58" font-size="11" fill="#0f172a">num_serie</text> <rect x="708" y="47" width="24" height="14" rx="3" fill="#2563eb"/> <text x="720" y="57" text-anchor="middle" font-size="9" font-weight="bold" fill="#ffffff">PK</text> <text x="550" y="78" font-size="11" fill="#0f172a">marca</text> <text x="550" y="98" font-size="11" fill="#0f172a">modelo</text> <text x="550" y="118" font-size="11" fill="#0f172a">fecha_compra</text> <text x="550" y="138" font-size="11" fill="#0f172a">estado</text> <line x1="540" y1="64" x2="740" y2="64" stroke="#e2e8f0" stroke-width="1"/> <!-- PRESTAMO --> <rect x="250" y="150" width="210" height="144" rx="6" fill="#ffffff" stroke="#334155" stroke-width="1.3"/> <rect x="250" y="150" width="210" height="24" fill="#e2e8f0"/> <text x="355" y="167" text-anchor="middle" font-size="12" font-weight="bold" fill="#0f172a">PRESTAMO</text> <rect x="250" y="174" width="210" height="20" fill="#eef2ff"/> <text x="260" y="188" font-size="11" fill="#0f172a">id_prestamo</text> <rect x="428" y="177" width="24" height="14" rx="3" fill="#2563eb"/> <text x="440" y="187" text-anchor="middle" font-size="9" font-weight="bold" fill="#ffffff">PK</text> <rect x="250" y="194" width="210" height="20" fill="#fffbeb"/> <text x="260" y="208" font-size="11" fill="#0f172a">nia</text> <rect x="428" y="197" width="24" height="14" rx="3" fill="#d97706"/> <text x="440" y="207" text-anchor="middle" font-size="9" font-weight="bold" fill="#ffffff">FK</text> <rect x="250" y="214" width="210" height="20" fill="#fffbeb"/> <text x="260" y="228" font-size="11" fill="#0f172a">num_serie</text> <rect x="428" y="217" width="24" height="14" rx="3" fill="#d97706"/> <text x="440" y="227" text-anchor="middle" font-size="9" font-weight="bold" fill="#ffffff">FK</text> <text x="260" y="248" font-size="11" fill="#0f172a">fecha_prestamo</text> <text x="260" y="268" font-size="11" fill="#0f172a">fecha_dev_prevista</text> <text x="260" y="288" font-size="11" fill="#0f172a">fecha_dev_real</text> <!-- INCIDENCIA --> <rect x="520" y="280" width="210" height="104" rx="6" fill="#ffffff" stroke="#334155" stroke-width="1.3"/> <rect x="520" y="280" width="210" height="24" fill="#e2e8f0"/> <text x="625" y="297" text-anchor="middle" font-size="12" font-weight="bold" fill="#0f172a">INCIDENCIA</text> <rect x="520" y="304" width="210" height="20" fill="#eef2ff"/> <text x="530" y="318" font-size="11" fill="#0f172a">id_incidencia</text> <rect x="698" y="307" width="24" height="14" rx="3" fill="#2563eb"/> <text x="710" y="317" text-anchor="middle" font-size="9" font-weight="bold" fill="#ffffff">PK</text> <rect x="520" y="324" width="210" height="20" fill="#fffbeb"/> <text x="530" y="338" font-size="11" fill="#0f172a">num_serie</text> <rect x="698" y="327" width="24" height="14" rx="3" fill="#d97706"/> <text x="710" y="337" text-anchor="middle" font-size="9" font-weight="bold" fill="#ffffff">FK</text> <text x="530" y="358" font-size="11" fill="#0f172a">descripcion</text> <text x="530" y="378" font-size="11" fill="#0f172a">fecha</text> <!-- FK: PRESTAMO.nia -> ALUMNO.nia --> <polyline points="250,204 225,204 225,64 202,64" fill="none" stroke="#2563eb" stroke-width="1.5"/> <polygon points="200,64 212,59 212,69" fill="#2563eb"/> <!-- FK: PRESTAMO.num_serie -> PORTATIL.num_serie --> <polyline points="460,224 500,224 500,50 538,50" fill="none" stroke="#059669" stroke-width="1.5"/> <polygon points="540,50 528,45 528,55" fill="#059669"/> <!-- FK: INCIDENCIA.num_serie -> PORTATIL.num_serie --> <polyline points="520,334 505,334 505,60 538,60" fill="none" stroke="#059669" stroke-width="1.5"/> <polygon points="540,60 528,55 528,65" fill="#059669"/> <text x="20" y="398" font-size="10" fill="#64748b">PK = clave primaria · FK = clave ajena · la flecha va de la clave ajena a la clave primaria referenciada</text> </svg>

Figura 2. Modelo relacional en 3FN: cuatro tablas con clave primaria (PK) y claves ajenas (FK); las flechas muestran las referencias PRESTAMO.nia→ALUMNO, PRESTAMO.num_serie→PORTATIL e INCIDENCIA.num_serie→PORTATIL.

Definición física (DDL MariaDB/MySQL, motor InnoDB):

CREATE TABLE alumno (
    nia        CHAR(8)      PRIMARY KEY,            -- NIA: 8 dígitos GVA
    nombre     VARCHAR(60)  NOT NULL,
    apellidos  VARCHAR(120) NOT NULL,
    grupo      VARCHAR(20)  NOT NULL
) ENGINE=InnoDB;

CREATE TABLE portatil (
    num_serie    VARCHAR(40) PRIMARY KEY,
    marca        VARCHAR(40) NOT NULL,
    modelo       VARCHAR(60) NOT NULL,
    fecha_compra DATE,
    estado       ENUM('disponible','prestado','baja') NOT NULL DEFAULT 'disponible'
) ENGINE=InnoDB;

CREATE TABLE prestamo (
    id_prestamo        INT AUTO_INCREMENT PRIMARY KEY,
    nia                CHAR(8)     NOT NULL,
    num_serie          VARCHAR(40) NOT NULL,
    fecha_prestamo     DATE        NOT NULL,
    fecha_dev_prevista DATE        NOT NULL,
    fecha_dev_real     DATE        NULL,           -- NULL = aún no devuelto
    CONSTRAINT fk_prestamo_alumno
        FOREIGN KEY (nia) REFERENCES alumno(nia)
        ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_prestamo_portatil
        FOREIGN KEY (num_serie) REFERENCES portatil(num_serie)
        ON UPDATE CASCADE ON DELETE RESTRICT
) ENGINE=InnoDB;

CREATE TABLE incidencia (
    id_incidencia INT AUTO_INCREMENT PRIMARY KEY,
    num_serie     VARCHAR(40)  NOT NULL,
    descripcion   VARCHAR(255) NOT NULL,
    fecha         DATE         NOT NULL,
    CONSTRAINT fk_incidencia_portatil
        FOREIGN KEY (num_serie) REFERENCES portatil(num_serie)
        ON UPDATE CASCADE ON DELETE CASCADE
) ENGINE=InnoDB;

Justificación de las políticas referenciales.

  • prestamo → alumno/portatil: ON DELETE RESTRICT. No debe poder borrarse un alumno o un portátil que conserva historial de préstamos (integridad y trazabilidad del programa de digitalización). ON UPDATE CASCADE por si se corrige un NIA o número de serie.
  • incidencia → portatil: ON DELETE CASCADE es admisible (si se da de baja físicamente y se elimina el portátil, sus incidencias carecen de sentido); en un sistema con auditoría se preferiría RESTRICT y dar de baja con estado='baja'.

Justificación de la 3FN. Una tabla está en 3FN si está en 2FN y ningún atributo no primo depende transitivamente de la clave (todo atributo no clave depende de la clave, de toda la clave y de nada más que la clave).

Formalmente, las únicas dependencias funcionales de cada relación parten de su clave completa, por ejemplo:

\text{nia} \rightarrow \{\text{nombre},\ \text{apellidos},\ \text{grupo}\} \text{num\_serie} \rightarrow \{\text{marca},\ \text{modelo},\ \text{fecha\_compra},\ \text{estado}\}

No existe ninguna dependencia transitiva del tipo \text{nia} \rightarrow \text{grupo} \rightarrow X: como el grupo se guarda solo como atributo descriptivo (sin atributos propios del grupo), no hay ningún X que dependa de \text{grupo} en la tabla.

  • 1FN: todos los atributos son atómicos (no hay listas ni grupos repetitivos como «varias incidencias en una celda»).
  • 2FN: todas las claves primarias son simples (nia, num_serie, id_prestamo, id_incidencia), luego no puede haber dependencias parciales de una clave compuesta. Inmediata.
  • 3FN: se comprueba la ausencia de dependencias transitivas:
    • En alumno: nombre, apellidos y grupo dependen directamente de nia. Atención al grupo: no se almacena ningún atributo del grupo (p. ej. tutor, aula) en esta tabla; si lo hubiera, nia → grupo → tutor sería transitiva y obligaría a extraer una tabla GRUPO. Como solo guardamos el nombre del grupo como atributo descriptivo del alumno, no hay transitividad.
    • En portatil: marca, modelo, fecha_compra, estado dependen de num_serie. No se deriva ningún atributo de otro (p. ej. no guardamos «fabricante de la marca»), luego no hay transitividad.
    • En prestamo: las tres fechas y las dos FK dependen únicamente de id_prestamo. Los datos del alumno o del portátil no se replican aquí (solo sus claves), evitando la dependencia transitiva id_prestamo → nia → nombre.
    • En incidencia: descripcion y fecha dependen de id_incidencia; no hay derivados.

Por tanto, las cuatro relaciones están en 3FN (y de hecho en BCNF, al ser claves candidatas simples).


Cuestión 3 — Consultas SQL

3.1 — Portátiles disponibles, ordenados por marca y modelo:

SELECT num_serie, marca, modelo, fecha_compra
FROM   portatil
WHERE  estado = 'disponible'
ORDER  BY marca, modelo;

3.2 — Nº de préstamos por grupo, solo grupos con más de 10 (JOIN + GROUP BY + HAVING):

SELECT a.grupo,
       COUNT(*) AS total_prestamos
FROM   prestamo p
JOIN   alumno   a ON a.nia = p.nia
GROUP  BY a.grupo
HAVING COUNT(*) > 10
ORDER  BY total_prestamos DESC;

Se usa HAVING (no WHERE) porque el filtro actúa sobre el resultado de la agregación COUNT(*), que no existe a nivel de fila.

3.3 — Préstamos vencidos no devueltos (NIA, nombre, nº de serie):

SELECT a.nia,
       a.nombre,
       p.num_serie,
       p.fecha_dev_prevista
FROM   prestamo p
JOIN   alumno   a ON a.nia = p.nia
WHERE  p.fecha_dev_real IS NULL          -- aún no devuelto
  AND  p.fecha_dev_prevista < CURDATE(); -- la fecha prevista ya pasó

Clave: comprobar la no devolución con IS NULL (no = NULL, que en SQL siempre es desconocido) y comparar la fecha prevista con CURDATE().

3.4 — Portátiles que nunca tuvieron incidencias (dos soluciones):

Con LEFT JOIN ... IS NULL (antijunción):

SELECT po.num_serie, po.marca, po.modelo
FROM   portatil po
LEFT   JOIN incidencia i ON i.num_serie = po.num_serie
WHERE  i.id_incidencia IS NULL;

Equivalente con subconsulta NOT EXISTS (suele ser la más eficiente en InnoDB):

SELECT po.num_serie, po.marca, po.modelo
FROM   portatil po
WHERE  NOT EXISTS (
    SELECT 1 FROM incidencia i
    WHERE i.num_serie = po.num_serie
);

Cuestión 4 — Trigger de integridad

DELIMITER //

CREATE TRIGGER trg_prestamo_before_insert
BEFORE INSERT ON prestamo
FOR EACH ROW
BEGIN
    DECLARE v_estado VARCHAR(20);

    -- Recuperar el estado actual del portátil que se quiere prestar
    SELECT estado INTO v_estado
    FROM   portatil
    WHERE  num_serie = NEW.num_serie;

    -- Si no está disponible, abortar la operación
    IF v_estado IS NULL OR v_estado <> 'disponible' THEN
        SIGNAL SQLSTATE '45000'
            SET MESSAGE_TEXT = 'El portatil no esta disponible para prestamo';
    END IF;

    -- Si es válido, marcar el portátil como prestado
    UPDATE portatil
    SET    estado = 'prestado'
    WHERE  num_serie = NEW.num_serie;
END//

DELIMITER ;

¿BEFORE o AFTER, y por qué? Se usa BEFORE INSERT:

  1. La validación debe ejecutarse antes de materializar la fila, para poder abortar la inserción con SIGNAL SQLSTATE '45000' (en MySQL/MariaDB un error en un trigger BEFORE cancela la sentencia y, dentro de una transacción InnoDB, deshace sus efectos: la operación es atómica).
  2. Si usáramos AFTER, el préstamo ya estaría insertado cuando lanzáramos el error; aunque el SIGNAL también revierte la sentencia en InnoDB, BEFORE expresa mejor la intención de «portero» (gatekeeper) y evita trabajo innecesario.
  3. La actualización estado='prestado' se hace sobre otra tabla (portatil), no sobre la fila NEW que se está insertando, por lo que es perfectamente legal en un trigger BEFORE (la restricción de no poder modificar NEW en AFTER, o de no tocar la propia tabla disparadora, no aplica aquí).

Conviene complementarlo con un trigger AFTER UPDATE de devolución (cuando fecha_dev_real pasa de NULL a una fecha) que devuelva el portátil a disponible.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 300" font-family="ui-sans-serif, system-ui, Arial, sans-serif"> <!-- estado inicial --> <circle cx="150" cy="24" r="6" fill="#334155"/> <line x1="150" y1="30" x2="150" y2="48" stroke="#334155" stroke-width="1.6"/> <polygon points="150,50 145,40 155,40" fill="#334155"/> <text x="168" y="28" font-size="10" fill="#64748b">alta</text> <!-- disponible --> <rect x="60" y="50" width="180" height="56" rx="14" fill="#ecfdf5" stroke="#059669" stroke-width="1.8"/> <text x="150" y="84" text-anchor="middle" font-size="14" font-weight="bold" fill="#0f172a">disponible</text> <!-- prestado --> <rect x="520" y="50" width="180" height="56" rx="14" fill="#eff6ff" stroke="#2563eb" stroke-width="1.8"/> <text x="610" y="84" text-anchor="middle" font-size="14" font-weight="bold" fill="#0f172a">prestado</text> <!-- baja --> <rect x="290" y="210" width="180" height="56" rx="14" fill="#fef2f2" stroke="#dc2626" stroke-width="1.8"/> <text x="380" y="244" text-anchor="middle" font-size="14" font-weight="bold" fill="#0f172a">baja</text> <!-- disponible -> prestado --> <line x1="240" y1="72" x2="518" y2="72" stroke="#334155" stroke-width="1.6"/> <polygon points="520,72 508,67 508,77" fill="#334155"/> <text x="380" y="64" text-anchor="middle" font-size="11" fill="#059669">nuevo préstamo (trigger)</text> <!-- prestado -> disponible --> <line x1="520" y1="90" x2="242" y2="90" stroke="#334155" stroke-width="1.6"/> <polygon points="240,90 252,85 252,95" fill="#334155"/> <text x="380" y="104" text-anchor="middle" font-size="11" fill="#2563eb">devolución</text> <!-- disponible -> baja --> <line x1="150" y1="106" x2="300" y2="207" stroke="#334155" stroke-width="1.6"/> <polygon points="305,210 291,205 299,216" fill="#334155"/> <text x="150" y="168" font-size="11" fill="#dc2626">dar de baja</text> <!-- prestado -> baja --> <line x1="610" y1="106" x2="460" y2="207" stroke="#334155" stroke-width="1.6"/> <polygon points="455,210 469,205 461,216" fill="#334155"/> <text x="520" y="168" font-size="11" fill="#dc2626">dar de baja</text> </svg>

Figura 3. Ciclo de estados del portátil: disponible↔prestado según préstamo/devolución, y transición a baja desde cualquiera de los dos estados activos.


Cuestión 5 — Mini-cuestión: estado «a mano» vs. vista derivada

Problema de integridad. El campo portatil.estado es un dato derivado (redundante): su valor «verdadero» puede deducirse de si existe un préstamo abierto (fecha_dev_real IS NULL). Si se mantiene manualmente, aparece el riesgo de anomalía de actualización / inconsistencia: basta olvidar actualizarlo al prestar o al devolver para que la BD afirme que un portátil está disponible cuando en realidad tiene un préstamo abierto (o viceversa). El dato almacenado y el dato real divergen, y dos «fuentes de verdad» (la columna y la tabla prestamo) se contradicen. Es exactamente la redundancia que la normalización trata de evitar.

Alternativa basada en una VISTA. En lugar de almacenar el estado «activo/prestado», se deriva en tiempo de consulta a partir de los préstamos abiertos, garantizando que siempre es coherente:

CREATE OR REPLACE VIEW v_portatil_estado AS
SELECT  po.num_serie,
        po.marca,
        po.modelo,
        CASE
            WHEN po.estado = 'baja' THEN 'baja'
            WHEN EXISTS (
                SELECT 1 FROM prestamo p
                WHERE p.num_serie = po.num_serie
                  AND p.fecha_dev_real IS NULL
            ) THEN 'prestado'
            ELSE 'disponible'
        END AS estado_calculado
FROM    portatil po;

Así, disponible/prestado deja de mantenerse a mano y se calcula siempre desde la única fuente de verdad (la tabla prestamo); solo baja (estado físico, no derivable de los préstamos) sigue requiriendo un marcado explícito. La vista no se puede «desincronizar», a costa de un pequeño coste de cálculo en cada consulta. En la práctica suele combinarse el trigger (para mantener la columna y poder indexarla/consultarla rápido) con esta vista de verificación que permite auditar inconsistencias.

Practica con supuestos como este

Genera supuestos de tu especialidad y comunidad en cualquiera de los cuatro formatos, resuélvelos y recibe corrección con nota al instante.