El correo de Helena es el contexto; esto es el contrato. Un requisito sirve para algo solo si se puede comprobar: "el sistema debe gestionar bien los préstamos" no es un requisito, es un deseo. "Un ejemplar no puede aparecer en dos préstamos con fecha_devolucion IS NULL, y la base de datos debe rechazar el intento" sí lo es, porque hay una forma objetiva de saber si se cumple: intentarlo y ver si falla.

Esta lección es esa especificación, agrupada en seis bloques —datos, integridad, consulta, rendimiento, seguridad y entrega— y cerrada con la rúbrica con la que se corrige el proyecto, para que puedas autoevaluarte antes de darlo por terminado. Dentro de cada bloque hay dos capas: lo que no es negociable, porque es lo que el proyecto pretende enseñar, y lo que queda a tu criterio, porque diseñar es elegir y en 12-04 verás que varias soluciones distintas son igual de correctas.

Contenido

  1. Convenciones obligatorias
  2. Requisitos de datos (RD)
  3. Requisitos de integridad (RI)
  4. Requisitos de consulta (RC): las 15 consultas
  5. Requisitos de rendimiento (RP)
  6. Requisitos de seguridad (RS)
  7. Requisitos de entrega (RE)
  8. Criterios de calidad
  9. Rúbrica de evaluación
  10. Errores Comunes y Consejos
  11. Ejercicios
  12. Conclusión

  1. Convenciones obligatorias

Son las del curso, y se dan por sabidas (05-01, 11-02). Se listan porque forman parte de la corrección:

Elemento Convención
Identificadores snake_case, en singular para columnas y plural para tablas, sin tildes ni ñ
Clave primaria id INTEGER GENERATED BY DEFAULT AS IDENTITY (o compuesta en tablas puente puras)
Clave foránea <tabla_referenciada>_id, siempre con ON DELETE explícito
Restricciones Con nombre: pk_, fk_, uq_, chk_; índices idx_
Dinero NUMERIC(10,2). Nunca coma flotante
Fechas DATE, salvo donde haga falta el instante
Nulabilidad NOT NULL por defecto; cada NULL permitido es una decisión documentada
Estilo de consulta AS explícito en alias de columna, alias de tabla por inicial, una cláusula por línea
Fecha de referencia DATE '2026-06-30' en lugar de CURRENT_DATE, para que los resultados sean reproducibles

  1. Requisitos de datos (RD)

Estas son las entidades mínimas obligatorias. Puedes añadir columnas, y puedes añadir tablas si las justificas; lo que no puedes es quitar ninguna de estas ni fusionar dos.

# Entidad Atributos imprescindibles Notas
RD-01 sedes nombre (único), dirección, fecha de apertura 3 filas mínimo
RD-02 materias nombre (único) Equivalente a categorias
RD-03 editoriales nombre (único), país Equivalente a proveedores
RD-04 autores nombre, apellidos, nacionalidad, año de nacimiento 8 filas mínimo
RD-05 obras título, materia, editorial, año de publicación, ISBN Sin columna de existencias
RD-06 obras_autores obra, autor, rol, orden de firma N:M obligatoria, con PK compuesta
RD-07 ejemplares obra, sede, código de barras (único), fecha de adquisición, estado físico Una fila por objeto físico
RD-08 socios nombre, apellidos, documento (único), fecha de nacimiento, tipo, estado, sede de alta, fecha de alta tipo y estado con dominio cerrado
RD-09 bibliotecarios nombre, apellidos, puesto, sede, responsable, email (único) Jerarquía reflexiva obligatoria
RD-10 prestamos ejemplar, socio, bibliotecario, fecha de préstamo, fecha prevista, fecha real de devolución, renovaciones Una fila por ejemplar prestado
RD-11 reservas obra, socio, sede de recogida, fecha de reserva, estado, fechas de aviso y cierre Cuelga de obras
RD-12 multas préstamo (único), importe, días de retraso, fecha de generación, fecha de pago Tabla propia, no columna

Lo que no es negociable, y por qué:

  1. La separación obra / ejemplar (RD-05 + RD-07). Es el objeto del proyecto. Una sola tabla libros con un contador es un suspenso automático, por bien escrito que esté el resto.
  2. La N:M obras_autores (RD-06). Una columna autor en obras no permite dos autores, y una autor1/autor2 es el antipatrón de las columnas repetidas (01-05). Aquí sí, PK compuesta (obra_id, autor_id) sin id propio: es una tabla puente pura y esa era la alternativa que 05-01 describió.
  3. La jerarquía de bibliotecarios (RD-09). responsable_id referenciando a la propia tabla, nulable solo en la dirección. Es lo que alimenta el SELF JOIN de 03-06 y la CTE recursiva de 10-02.
  4. El histórico que no se borra (RD-10). Ningún DELETE sobre prestamos. Las bajas de socios y de ejemplares son lógicas, y las FK hacia prestamos deben impedir físicamente el borrado del padre.
  5. prestamos cuelga de ejemplares y reservas cuelga de obras. Invertir cualquiera de las dos rompe el modelo.

Lo que queda a tu criterio: si editoriales es tabla o columna de texto (se pide tabla, pero acepta discusión), si obras lleva idioma o número de páginas, si socios guarda teléfono, si ejemplares guarda la signatura topográfica, si reservas tiene tabla de estados o CHECK, y cómo distingues un ejemplar extraviado de uno dado de baja.

Volumen mínimo de datos de prueba: 3 sedes, 6 materias, 5 editoriales, 8 autores, 10 obras, 15 ejemplares, 12 socios, 25 préstamos, 4 reservas y 4 multas. Menos que eso no permite comprobar las consultas; mucho más, no aporta nada a mano. (El juego de datos de referencia de 12-03 usa 3 / 6 / 5 / 10 / 12 / 20 / 15 / 36 / 5 / 6.)

  1. Requisitos de integridad (RI)

Todas estas reglas deben estar declaradas en la base de datos. Que la aplicación también las compruebe está bien; que solo la aplicación las compruebe es exactamente lo que 05-01 llamaba "confiar en que todo el mundo se acuerde de validar".

# Regla Cómo se declara
RI-01 La fecha prevista es posterior a la de préstamo CHECK (fecha_prevista > fecha_prestamo)
RI-02 La fecha de devolución no es anterior a la de préstamo CHECK (fecha_devolucion IS NULL OR fecha_devolucion >= fecha_prestamo)
RI-03 Un ejemplar no puede estar en dos préstamos activos El punto difícil. Ver abajo
RI-04 Las renovaciones están entre 0 y el máximo (RN-03) CHECK sobre el rango; el tope por tipo, en la lógica
RI-05 El importe de una multa nunca es negativo, y sus días de retraso son > 0 Dos CHECK
RI-06 Una multa no se puede pagar antes de generarse CHECK (fecha_pago IS NULL OR fecha_pago >= fecha_generacion)
RI-07 Un préstamo tiene como mucho una multa UNIQUE (prestamo_id) en multas
RI-08 El ISBN, si existe, es único UNIQUE (isbn), nulable
RI-09 El código de barras del ejemplar es único y obligatorio NOT NULL UNIQUE
RI-10 El tipo y el estado del socio pertenecen a su dominio CHECK (... IN (...))
RI-11 El estado del ejemplar y el de la reserva, igual Dos CHECK
RI-12 Un bibliotecario no puede ser su propio responsable CHECK (responsable_id <> id)
RI-13 Un socio no puede tener dos reservas vivas de la misma obra Índice único parcial sobre los estados vivos
RI-14 No se puede borrar un socio, un ejemplar ni una obra con historial ON DELETE RESTRICT en las FK de prestamos

El punto difícil: RI-03

Este requisito merece su apartado porque es donde se ve si has entendido el módulo 8. La regla es: puede haber muchos préstamos del ejemplar 1 —su historia entera— pero como mucho uno con fecha_devolucion IS NULL.

Las cuatro salidas posibles, y por qué tres no valen:

Intento Problema
UNIQUE (ejemplar_id) Prohíbe el histórico: el ejemplar 1 no podría prestarse dos veces en su vida
UNIQUE (ejemplar_id, fecha_devolucion) No funciona: NULL no es igual a NULL (04-03, 05-01), así que admite infinitos préstamos activos
Un CHECK con subconsulta Ilegal: un CHECK no puede consultar otras filas ni otras tablas (05-01)
Una columna activo BOOLEAN con UNIQUE parcial Funciona, pero duplica información: activo y fecha_devolucion IS NULL dirían lo mismo y podrían contradecirse

La solución que pide el proyecto es un índice único parcial (08-02): un índice que solo contiene las filas que cumplen una condición, y que por tanto solo impone unicidad entre ellas.

CREATE UNIQUE INDEX uq_prestamo_activo_por_ejemplar
    ON prestamos (ejemplar_id)
    WHERE fecha_devolucion IS NULL;

Se lee literalmente: "entre los préstamos sin devolver, el ejemplar_id es único". Es declarativo, lo hace cumplir el motor, ocupa solo tantas entradas como préstamos vivos haya —seis, no cientos de miles— y de propina acelera todas las consultas de préstamos activos. En 12-03 se implementa y en 12-04 se compara con la alternativa de la restricción EXCLUDE.

Nota de dialecto: los índices parciales son de PostgreSQL, SQLite y (con matices) SQL Server, donde se llaman filtered indexes. MySQL no los tiene, y allí la solución habitual es una columna generada con NULL cuando el préstamo está cerrado, más un UNIQUE sobre ella — porque UNIQUE ignora los nulos.

  1. Requisitos de consulta (RC): las 15 consultas

Estas quince consultas son el entregable 03-consultas.sql. Están ordenadas por dificultad creciente y cada una lleva la técnica que ejercita y la lección donde se enseñó, para que sepas dónde volver si te atascas. Todas usan DATE '2026-06-30' como fecha de referencia.

# Consulta Técnica Lección
RC-01 Obras publicadas desde 2015, con su ISBN, ordenadas por año descendente y título WHERE, ORDER BY con desempate 02-04, 02-06
RC-02 Ejemplares de una sede dada, con el título de su obra y su materia JOIN de cuatro tablas 03-02
RC-03 Préstamos activos: socio, obra, sede, días fuera y días de retraso JOIN + IS NULL + aritmética de fechas 03-02, 04-03, 06-03
RC-04 Los tres huecos del sistema: socios sin préstamos, obras sin ejemplares y ejemplares nunca prestados, en un solo resultado Anti-join + UNION ALL 03-03, 03-07, 07-03
RC-05 Materias con 5 o más préstamos, con su duración media GROUP BY + HAVING 04-05, 04-06
RC-06 Disponibilidad por obra: ejemplares totales, habilitados, prestados ahora y disponibles Agregado condicional con FILTER 04-04, 06-05
RC-07 Obras firmadas por más de un autor, con sus autores en una sola celda y en orden de firma N:M + string_agg 03-02, 04-04
RC-08 Multas por tipo de socio: total, cobrado y pendiente, con la tasa de retraso FILTER + COALESCE + métrica definida 06-04, 11-04
RC-09 Socios con más préstamos que la media de su tipo Subconsulta correlacionada 07-02, 07-03
RC-10 Cola de reservas en espera, con la posición de cada socio en la cola de su obra ROW_NUMBER() con partición 10-03
RC-11 Las 3 obras más prestadas de cada sede LATERAL (o ventana filtrada) 07-04
RC-12 Préstamos por mes de los últimos 12 meses, sin huecos, con acumulado y media móvil de 3 generate_series + LEFT JOIN + ventanas 11-01, 10-03
RC-13 Los 3 socios más lectores de cada sede, con desempate explícito RANK / ROW_NUMBER en partición 10-03
RC-14 Organigrama de bibliotecarios con su nivel y su ruta jerárquica CTE recursiva 10-02
RC-15 Informe pivotado: préstamos por sede (filas) y materia (columnas), sin perder ninguna sede Pivote con FILTER + LEFT JOIN 06-05, 11-04

Cinco condiciones que se aplican a las quince:

  1. Cada consulta va precedida de un comentario que diga qué pregunta responde y, si maneja una métrica, cómo la define (11-04). "Préstamos" ¿incluye los activos? ¿Y los de socios de baja? Escríbelo.
  2. Ninguna consulta puede perder filas por un JOIN mal elegido. RC-06, RC-12 y RC-15 tienen que enseñar los ceros: la obra sin ejemplares, el mes sin préstamos y la sede sin nada en una materia.
  3. Todo ORDER BY de un ranking necesita desempate. Sin él, dos ejecuciones pueden devolver órdenes distintos y el informe deja de ser reproducible.
  4. Cada consulta entregada va con su resultado. No basta el SQL: hay que haberlo ejecutado.
  5. Ninguna consulta usa SELECT *. Columnas explícitas, siempre (11-02).

  1. Requisitos de rendimiento (RP)

El proyecto se prueba con decenas de filas, pero se diseña para decenas de miles de socios y cientos de miles de préstamos. Los índices se justifican con ese tamaño, no con el del fichero de pruebas.

# Requisito
RP-01 Índice en todas las claves foráneas que se usan para navegar: PostgreSQL indexa la PK, no la FK (08-01). prestamos(ejemplar_id), prestamos(socio_id), ejemplares(obra_id), ejemplares(sede_id), obras(materia_id)
RP-02 El índice único parcial de RI-03, que además resuelve la consulta de préstamos activos
RP-03 Un índice que sirva a la consulta de vencidos y a la serie temporal: sobre prestamos(fecha_prestamo) y sobre las columnas por las que se filtra el vencimiento
RP-04 Un índice para la búsqueda por título, con la decisión razonada entre B-tree sobre una expresión, pg_trgm o búsqueda de texto completo (08-03)
RP-05 Cada índice se justifica con la consulta concreta que lo aprovecha. Un índice sin consulta que lo use es un índice que solo frena las escrituras (08-02)
RP-06 El informe incluye el EXPLAIN de al menos dos consultas antes y después de crear su índice, con el cambio de plan comentado (08-05)
RP-07 Se declara qué se ha decidido no indexar y por qué

RP-05 y RP-07 son los que de verdad se corrigen. Poner diez índices es fácil; explicar por qué esos diez y no otros es lo que demuestra criterio.

  1. Requisitos de seguridad (RS)

Tres roles, según el modelo de 11-03: se conceden privilegios a roles de grupo y los usuarios se hacen miembros, nunca al revés.

# Rol Permisos
RS-01 bib_consulta SELECT sobre catálogo (obras, ejemplares, autores, materias, editoriales, sedes) y sobre las vistas públicas. Ningún acceso a socios, prestamos ni multas
RS-02 bib_mostrador Todo lo anterior, más SELECT/INSERT/UPDATE en prestamos, reservas y multas, y SELECT/UPDATE en socios. Sin DELETE en ninguna (RD-10)
RS-03 bib_admin Todo lo anterior, más DDL y gestión del catálogo. No es el propietario ni un superusuario

Y cuatro requisitos sobre los datos personales, que aquí no son un adorno: un historial de préstamos es un registro de lo que lee una persona, uno de los datos más sensibles que puede guardar una biblioteca.

  • RS-04. Ningún rol de aplicación tiene DELETE sobre el histórico, y ninguno es propietario de las tablas.
  • RS-05. Los informes de dirección se sirven de vistas agregadas que no exponen qué ha leído cada socio. Quien necesita el dato agregado no necesita el dato individual.
  • RS-06. La baja de un socio es lógica; la anonimización posterior (sustituir nombre, documento y email por valores neutros conservando el id) debe estar prevista, para poder cumplir una petición de supresión sin destruir la estadística.
  • RS-07. Los datos de prueba son íntegramente ficticios: nombres inventados, documentos con formato válido pero inexistentes y correos en @example.com, que es un dominio reservado precisamente para esto. No uses jamás datos reales, ni siquiera los tuyos, en un fichero que va a acabar en un repositorio.

Opcionalmente, y como ejercicio de 11-03: implementa RLS para que un bibliotecario solo vea los socios de su sede. Suma en la rúbrica, pero no es obligatorio y no compensa entregarlo mal.

  1. Requisitos de entrega (RE)

Fichero Contenido Debe cumplir
01-esquema.sql DROP en orden inverso, CREATE TABLE en orden de dependencias, restricciones nombradas, índices, vistas Idempotente: ejecutarlo dos veces deja el mismo estado
02-datos.sql Datos de prueba, en orden de dependencias, con setval final si insertas id explícitos Ejecutable después del esquema, sin errores
03-consultas.sql Las 15 consultas, numeradas y comentadas Solo lectura: ni un INSERT, ni un UPDATE
04-informe.md El informe, según 12-05 Entre 4 y 8 páginas

RE-01. Los ficheros se ejecutan en orden: 01, 02, 03. La secuencia completa debe funcionar de un tirón sobre una base vacía:

createdb biblioteca
psql -d biblioteca -f 01-esquema.sql
psql -d biblioteca -f 02-datos.sql
psql -d biblioteca -f 03-consultas.sql

RE-02. El esquema es idempotente por el mismo mecanismo que tiendaverde.sql (01-06): DROP TABLE IF EXISTS ... CASCADE al principio, en orden inverso a las dependencias. Es lo correcto para un script de creación desde cero; para un sistema en producción serían migraciones versionadas (05-06), y decirlo en el informe suma.

RE-03. Los ficheros van en un repositorio git con su README.md (12-05), sin credenciales de ningún tipo.

RE-04. Cada consulta de 03-consultas.sql lleva encima su número RC-nn, la pregunta que responde y las lecciones que aplica.

  1. Criterios de calidad

Estos no son requisitos con número: son la diferencia entre un proyecto que funciona y uno que además está bien hecho. Salen íntegros de 11-02.

  • Formato uniforme. Palabras clave en mayúscula, una cláusula por línea, alias de tabla cortos y consistentes, sangría estable. Si dos consultas del mismo fichero se ven distintas, se nota.
  • Nombres que no necesitan comentario. fecha_devolucion sí; fd no. uq_prestamo_activo_por_ejemplar sí; idx3 no.
  • Comentarios que explican el porqué, no el qué. -- El LEFT JOIN es obligatorio: hay obras sin ningún ejemplar es útil. -- Une obras con ejemplares es ruido.
  • Ni una consulta sin ejecutar. Un 03-consultas.sql con una consulta que da error es el fallo más caro de todos, porque cuesta cero evitarlo.
  • Ni una definición implícita. Cada métrica del informe dice qué incluye y qué excluye (11-04).
  • Nada de sobreingeniería. Tres triggers, cinco vistas materializadas y una tabla de auditoría en un proyecto de doce tablas no suman: restan, porque hay que mantenerlas y defenderlas.

  1. Rúbrica de evaluación

Úsala como lista de autoevaluación antes de entregar. La columna de peso indica cuánto cuenta cada bloque.

Criterio Peso Qué se mira
Modelo de datos 25 % Separación obra/ejemplar; N:M correcta; jerarquía reflexiva; normalización sin excesos; decisiones de desnormalización justificadas
Integridad 20 % Las 14 RI declaradas y nombradas; RI-03 resuelta con índice parcial; ON DELETE coherente con la semántica de cada relación
Consultas 25 % Las 15 ejecutan y son correctas; sin filas perdidas ni duplicadas; ordenaciones con desempate; métricas definidas
Rendimiento 10 % Índices justificados uno a uno con su consulta; dos EXPLAIN comentados; lo que se decide no indexar
Seguridad 10 % Tres roles con privilegio mínimo; sin DELETE sobre el histórico; datos personales ficticios y baja lógica
Entrega e informe 10 % Los cuatro ficheros ejecutan en orden; estilo uniforme; informe con modelo, decisiones, resultados y limitaciones

Cuatro fallos anulan el bloque entero, por muy bien que esté el resto:

  1. Una sola tabla libros en lugar de obras + ejemplares → modelo a cero.
  2. RI-03 sin resolver o resuelta solo en la aplicación → integridad a cero.
  3. Una consulta del entregable que da error al ejecutarse → consultas a cero.
  4. Datos personales reales en el repositorio → entrega a cero.

Errores Comunes y Consejos

  • Leer los requisitos una vez, al principio. Vuelve a esta lección al terminar cada bloque y tacha lo cumplido. La mitad de lo que se pierde en la rúbrica es material olvidado, no material mal hecho.
  • Tomar los mínimos como objetivos. "12 socios" es el suelo para que las consultas tengan sentido, no la meta. Pero más de un par de cientos de filas a mano es tiempo tirado: para volumen, generate_series (12-03).
  • Confundir requisito y solución. RI-03 dice qué debe cumplirse; el índice parcial es cómo lo resolvemos aquí. Si encuentras otra forma que también lo garantice en la base, es válida — y en 12-04 hay una.
  • Resolver en la aplicación lo que pide la base. "Ya lo comprueba mi código antes de insertar" no cumple RI-03. Dos peticiones simultáneas se cuelan por ahí, y eso es exactamente lo que el módulo 9 explicaba.
  • Escribir las quince consultas de un tirón y ejecutarlas al final. Ejecuta cada una en cuanto la escribas y comprueba el recuento de filas en cada JOIN (11-04). Un error en la tercera contamina las doce siguientes.
  • Consejo: convierte esta lección en un fichero requisitos.md del repositorio, con casillas de verificación. Es la checklist de 11-02 aplicada a tu propio proyecto, y es lo que consultará quien te corrija.
  • Consejo: escribe primero la consulta más difícil que veas (probablemente RC-11 o RC-12). Si el modelo aguanta la más difícil, aguanta las otras catorce; si no, mejor descubrirlo antes de cargar los datos.
  • Consejo: guarda la salida de cada consulta en un fichero. Cuando cambies el esquema o los datos, podrás comparar y ver qué se ha movido. Es la "cifra de control" de 11-04 aplicada al proyecto.

Ejercicios

Como en 12-01, son tareas del proyecto: llevan esbozo o rúbrica, no solución completa.

Tarea 1 — El plan de ataque

Convierte los requisitos en un plan de trabajo tuyo: una tabla con las tareas, su dependencia con las otras, la estimación en horas y el requisito que cierra cada una. Debe cubrir del modelo a la entrega. Si tu plan no incluye una tarea explícita de "generar datos de prueba con casos límite", está incompleto.

Tarea 2 — Predecir el punto difícil

Antes de escribir nada de SQL, razona sobre RI-03: (1) ¿por qué UNIQUE (ejemplar_id, fecha_devolucion) no funciona en PostgreSQL, y en qué motor sí funcionaría? (2) Escribe el INSERT exacto que debería fallar y el que debe seguir funcionando. (3) ¿Qué pasa con el índice parcial si mañana se decide que un ejemplar puede prestarse "en sala" al mismo tiempo que está prestado a domicilio?

Tarea 3 — Definir las métricas

Antes de RC-08, escribe la definición exacta de estas cinco métricas, diciendo qué incluye y qué no, al estilo de la tabla de definiciones de 11-04: préstamos del periodo, socio activo, tasa de retraso, deuda pendiente y obra disponible. Para cada una, indica además qué otra definición razonable existe y cómo cambiaría la cifra.

Soluciones

Rúbrica de la Tarea 1

Un plan aceptable tiene entre 8 y 12 tareas y respeta estas dependencias: modelo → DDL → datos → consultas → índices → vistas y seguridad → informe. Los dos errores de planificación que se repiten son dejar los datos de prueba para el final —y descubrir entonces que el modelo no permite representar un caso— y dejar el informe para el último día, cuando ya no recuerdas por qué tomaste la mitad de las decisiones. Escribe el informe a medida que decides.

Solución de la Tarea 2

(1) Porque en el estándar SQL, y en PostgreSQL, NULL no es igual a NULL, así que dos filas con (1, NULL) no se consideran duplicadas y el UNIQUE las admite las dos (04-03, 05-01). En SQL Server sí fallaría, porque trata todos los NULL como iguales a efectos del índice único — y en PostgreSQL 15+ se puede imitar con UNIQUE NULLS NOT DISTINCT, aunque para este caso el índice parcial sigue siendo mejor porque además indexa solo las filas vivas.

(2) Debe fallar un segundo préstamo abierto del mismo ejemplar, y debe seguir funcionando otro préstamo cerrado del mismo ejemplar:

-- ⚠️ INCORRECTA: el ejemplar 1 ya tiene un préstamo sin devolver
INSERT INTO prestamos (ejemplar_id, socio_id, bibliotecario_id, fecha_prestamo, fecha_prevista)
VALUES (1, 13, 5, DATE '2026-06-25', DATE '2026-07-16');

-- ✅ CORRECTA: es historia, no un préstamo vivo
INSERT INTO prestamos (ejemplar_id, socio_id, bibliotecario_id, fecha_prestamo, fecha_prevista, fecha_devolucion)
VALUES (1, 13, 5, DATE '2024-01-10', DATE '2024-01-31', DATE '2024-01-28');

La primera devuelve:

ERROR:  duplicate key value violates unique constraint "uq_prestamo_activo_por_ejemplar"
DETAIL:  Key (ejemplar_id)=(1) already exists.

(3) El índice dejaría de valer tal cual, porque ya no habría "como mucho un préstamo activo" sino "como mucho uno de cada modalidad". La solución sería añadir una columna modalidad e incluirla en el índice: ON prestamos (ejemplar_id, modalidad) WHERE fecha_devolucion IS NULL. Es un buen recordatorio de que una restricción codifica una regla de negocio concreta, y que cuando la regla cambia, la restricción cambia con ella — lo cual, por cierto, es una ventaja: si la regla viviera repartida por el código de la aplicación, nadie sabría dónde tocar.

Esbozo de la Tarea 3

Dos de las cinco, para fijar el nivel de detalle esperado:

Métrica Definición del proyecto Alternativa razonable
Préstamos del periodo Filas de prestamos con fecha_prestamo dentro del periodo, todos los estados, incluidos los que siguen abiertos y los de socios que luego se dieron de baja Contar solo los cerrados, para poder hablar de duración media. Da una cifra menor y no vale para medir demanda
Deuda pendiente SUM(importe) de multas con fecha_pago IS NULL. No incluye los préstamos vencidos sin devolver, que aún no han generado multa (RN-10) Incluir la deuda potencial de los vencidos, calculada a día de hoy. Es la cifra que interesa a dirección, y hay que llamarla por otro nombre para no mezclarla con la contable

La lección de fondo es la de 11-04: no hay una definición correcta, hay una definición escrita. Lo grave no es elegir mal; es publicar dos cifras distintas en el mismo informe sin decir en qué se diferencian.

Conclusión

Ya tienes el contrato del proyecto:

  • 12 requisitos de datos con las entidades mínimas y sus atributos. Lo innegociable: la separación obra / ejemplar, la N:M obras_autores con PK compuesta, la jerarquía reflexiva de bibliotecarios, el histórico que no se borra, y que prestamos cuelgue de ejemplares mientras reservas cuelga de obras.
  • 14 requisitos de integridad, todos declarados en la base y con nombre. El difícil es RI-03 —un ejemplar, un solo préstamo activo—, que no se puede resolver con UNIQUE (por los nulos), ni con CHECK (no puede mirar otras filas), y que se resuelve con un índice único parcial WHERE fecha_devolucion IS NULL.
  • 15 consultas ordenadas por dificultad, cada una con su técnica y su lección: del filtro simple al anti-join, del HAVING al agregado condicional, de la correlacionada al LATERAL, y de la serie temporal sin huecos al pivote, la ventana y la CTE recursiva. Con cinco condiciones transversales: comentario con la definición, nada de filas perdidas, desempate en los rankings, resultado adjunto y ni un SELECT *.
  • Rendimiento: índices en las FK que se navegan, el parcial de RI-03, uno para la serie temporal y otro para la búsqueda por título, cada uno justificado con la consulta que lo usa, dos EXPLAIN comentados y la lista de lo que decides no indexar.
  • Seguridad: tres roles de privilegio mínimo, sin DELETE sobre el histórico, informes servidos por vistas agregadas, baja lógica con anonimización prevista y datos íntegramente ficticios.
  • Entrega: cuatro ficheros que se ejecutan en orden sobre una base vacía, esquema idempotente, y la rúbrica de seis criterios —modelo 25 %, integridad 20 %, consultas 25 %, rendimiento 10 %, seguridad 10 %, entrega 10 %— con cuatro fallos que anulan su bloque entero.

Sabes qué hay que hacer y con qué se te va a medir. Falta el cómo. En la lección siguiente, Implementación del proyecto, está la guía de construcción en siete pasos: del enunciado al diagrama entidad-relación completo, con las decisiones difíciles justificadas —por qué ejemplares es tabla, por qué se guarda fecha_prevista en vez de calcularla, por qué las multas no son una columna y cómo se modela una cola—; el 01-esquema.sql comentado con el índice parcial explicado a fondo; cómo generar datos coherentes y qué casos límite deben contener; el método para escribir consultas sin equivocarse, con dos resueltas de ejemplo; cómo decidir los índices a partir de las consultas y no al revés; qué encapsular en vistas, procedimientos y triggers sin pasarse; y el cronograma de trabajo.

Curso de SQL

Módulo 1: Introducción a SQL

Módulo 2: Consultas básicas de SQL

Módulo 3: Trabajando con múltiples tablas

Módulo 4: Filtrado avanzado de datos

Módulo 5: Manipulación de datos

Módulo 6: Funciones avanzadas de SQL

Módulo 7: Subconsultas y consultas anidadas

Módulo 8: Índices y optimización de rendimiento

Módulo 9: Transacciones y concurrencia

Módulo 10: Temas avanzados

Módulo 11: SQL en la práctica

Módulo 12: Proyecto final

© Copyright 2026. Todos los derechos reservados