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:
- 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).
- 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.
- 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.