Módulo 17: Restricciones avanzadas

Objetivos del módulo

  • Reconocer las restricciones semánticas que un diagrama E/R no puede expresar y aprender a documentarlas de forma rigurosa
  • Dominar las cuatro restricciones entre relaciones de la notación de Chen extendida: exclusividad, exclusión, inclusividad e inclusión
  • Expresar restricciones sobre una sola relación: reflexivas, de roles distintos y cardinalidades condicionadas
  • Detectar y resolver las trampas de conexión: la trampa de abanico (fan trap) y la trampa de sima (chasm trap)
  • Analizar ciclos de relaciones para detectar redundancias y decidir cuándo una redundancia es aceptable
  • Modelar la dimensión temporal: atributos históricos, relaciones con fecha, entidades de historial y periodos de validez
  • Resolver el problema de las relaciones N:M que deben admitir repeticiones
  • Tratar correctamente los atributos derivados y la redundancia controlada
  • Redactar un catálogo de restricciones de negocio en lenguaje natural y prever cómo se implementará cada una

1. Lo que el diagrama no dice

1.1. El diagrama es necesario, pero no suficiente

Un diagrama E/R expresa muchas cosas: qué entidades hay, qué las identifica, cómo se relacionan y con qué cardinalidades. Pero hay reglas del negocio, a veces muy importantes, que no tienen símbolo en ninguna notación:

  • “El precio de venta de un producto nunca puede ser inferior a su precio de coste.”
  • “Un empleado no puede ser jefe de sí mismo.”
  • “La fecha de devolución de un préstamo es posterior a la fecha de préstamo.”
  • “Un socio no puede tener más de tres películas alquiladas a la vez.”
  • “Un médico solo puede atender consultas de su especialidad.”

📘 Concepto: Una restricción semántica (o restricción de integridad, integrity constraint) es una condición que deben cumplir los datos para representar correctamente la realidad. Las que el modelo puede expresar con su notación se llaman restricciones inherentes o estructurales (identificadores, cardinalidades, cobertura de jerarquías); las demás son restricciones explícitas y hay que documentarlas aparte.

Si no documentas las restricciones explícitas, se pierden: el diseñador lógico no las verá, el programador no las implementará y la base de datos aceptará datos incorrectos. Un esquema conceptual completo es siempre diagrama + diccionario de datos + lista de restricciones.

1.2. Clasificación de las restricciones

Tipo Qué restringe Ejemplo Dónde acabará
De dominio Los valores posibles de un atributo “La nota está entre 0 y 10” CHECK de columna, tipo de dato, dominio SQL
De obligatoriedad / unicidad Si un atributo es obligatorio o único “El email es único” NOT NULL, UNIQUE
Entre atributos de la misma ocurrencia Combinación de valores de una fila “fecha_fin ≥ fecha_inicio” CHECK de tabla
Entre ocurrencias de la misma entidad Combinación de varias filas “No hay dos reservas solapadas de la misma habitación” Trigger o aplicación
De cardinalidad condicionada Máximos o mínimos que dependen de algo “Un alumno de primer curso se matricula como máximo en 6 asignaturas” Trigger o aplicación
Entre relaciones Participación en unas relaciones según otras “Un vehículo pertenece a una persona o a una empresa” CHECK, clave ajena compuesta, trigger
Temporales / de transición Cambios de estado permitidos “Un pedido entregado no puede volver a pendiente” Trigger o aplicación
Derivación Cómo se calcula un dato “importe = cantidad × precio × (1 − descuento/100)” Columna virtual, vista, trigger

💡 Consejo: La última columna anticipa el diseño físico. Cuando transformes el modelo a SQL verás que las restricciones de una sola fila se implementan con CHECK, y las que implican varias filas o varias tablas suelen necesitar triggers, que se estudian en el Curso de Oracle PL/SQL, o lógica en la aplicación.

1.3. Cómo anotarlas

Hay tres formas complementarias de dejar constancia de una restricción:

  1. En el diagrama, si la notación tiene símbolo (cardinalidades, cobertura, las restricciones entre relaciones de la sección 2).
  2. Como nota numerada junto al diagrama: un recuadro con la etiqueta R1, R2… unido con línea discontinua al elemento afectado.
  3. En el catálogo de restricciones del diccionario de datos (módulo 14), con su enunciado completo, los elementos implicados y la forma de implementación prevista.

En este curso dibujamos las notas como un rectángulo unido con línea discontinua:

flowchart LR
    classDef nota fill:#fff8dc,stroke:#999,stroke-dasharray: 3 3
    EMPLEADO[EMPLEADO] ---|"(0,1) jefe"| dirige{"dirige<br/>1:N"}
    dirige ---|"(0,n) subordinado"| EMPLEADO
    EMPLEADO --- e_id([<u>num_empleado</u>])
    EMPLEADO --- e_sal([salario])
    R1["R1: un empleado no puede ser jefe de sí mismo"]:::nota
    R2["R2: el salario de un empleado no supera el de su jefe"]:::nota
    R1 -.- dirige
    R2 -.- e_sal

Cada nota debe ser breve, precisa y verificable: alguien debe poder decir, mirando los datos, si la restricción se cumple o no. “Los salarios deben ser razonables” no es una restricción; “el salario es mayor que 0 y no supera 10 000 €” sí lo es.


2. Restricciones entre relaciones

Cuando una misma entidad participa en varias relaciones, a menudo existen reglas que ligan su participación en unas con su participación en otras. La notación de Chen extendida, tal como la presentan de Miguel y Piattini, define cuatro restricciones de este tipo.

Restricción Idea en una frase Nivel
Exclusividad Cada ocurrencia participa en una u otra relación, no en las dos Ocurrencia de la entidad
Exclusión Un mismo par de ocurrencias no puede estar en las dos relaciones Par de ocurrencias
Inclusividad Para participar en una relación, la ocurrencia tiene que haber participado (n veces) en la otra Ocurrencia de la entidad
Inclusión Para que un par esté en una relación, el mismo par tiene que estar en la otra Par de ocurrencias

Fíjate en la simetría: exclusividad e inclusividad hablan de una ocurrencia de la entidad común; exclusión e inclusión hablan de un par concreto de ocurrencias. Es lo que más cuesta distinguir al principio, así que lo veremos con ocurrencias.

2.1. Notación del curso

Mermaid no dispone del “arco” que se dibuja en papel, así que en este curso las restricciones entre relaciones se representan con una línea discontinua entre los rombos afectados, etiquetada con el nombre de la restricción:

Restricción En papel (de Miguel y Piattini) En Mermaid (este curso)
Exclusividad Arco que corta las líneas de las relaciones junto a la entidad común r1 -.-\|"exclusividad"\| r2
Exclusión Línea discontinua entre los rombos r1 -.-\|"exclusión"\| r2
Inclusividad Flecha discontinua de la relación condicionada a la condicionante, con (mín, máx) r2 -.->\|"inclusividad (mín,máx)"\| r1
Inclusión Flecha discontinua de la relación incluida a la que la incluye r2 -.->\|"inclusión"\| r1

⚠️ Importante: La flecha de inclusividad e inclusión va desde la relación que impone la condición (la “posterior” o subordinada) hacia la relación que debe cumplirse antes. Léela como “R2 exige R1”.

2.2. Exclusividad

📘 Concepto: Dos o más relaciones R1, R2, …, Rn en las que participa una entidad E son exclusivas respecto a E si cada ocurrencia de E participa como mucho en una de ellas. Si además la participación es obligatoria (cada ocurrencia participa exactamente en una), se habla de exclusividad total.

💻 Ejemplo: En un registro de vehículos, cada VEHICULO pertenece a una PERSONA o a una EMPRESA, nunca a las dos a la vez, y siempre a alguna de ellas.

flowchart LR
    PERSONA[PERSONA] ---|"(0,1)"| posee_p{"posee_p<br/>1:N"}
    posee_p ---|"(0,n)"| VEHICULO[VEHICULO]
    EMPRESA[EMPRESA] ---|"(0,1)"| posee_e{"posee_e<br/>1:N"}
    posee_e ---|"(0,n)"| VEHICULO
    posee_p -.-|"exclusividad"| posee_e

Fíjate en las cardinalidades junto a PERSONA y EMPRESA: son (0,1), porque un vehículo concreto puede no tener persona propietaria (si es de una empresa), y viceversa. La obligatoriedad (“todo vehículo tiene propietario”) no puede expresarse con las mínimas de cada relación por separado: es la propia restricción de exclusividad total la que la recoge. Anótalo así: “exclusividad total: todo vehículo participa en exactamente una de las relaciones posee_p y posee_e”.

Ocurrencias válidas y no válidas:

Vehículo Persona propietaria Empresa propietaria ¿Válido?
1234-ABC 11111111H — Sí
5678-DEF — B12345678 Sí
9012-GHI 22222222J B12345678 No: participa en las dos
3456-JKL — — No si la exclusividad es total; sí si es parcial

Exclusividad, categoría y jerarquía

Este mismo problema puede modelarse de tres maneras, todas correctas, y conviene saber elegir:

Opción Cuándo es preferible
Exclusividad entre relaciones (este apartado) Hay una sola relación afectada (la propiedad) y las entidades son independientes
Categoría PROPIETARIO ⊆ PERSONA ∪ EMPRESA (módulo 11) El “propietario” participa en varias relaciones (posee, paga impuestos, recibe multas…) o tiene atributos propios
Jerarquía TITULAR → PERSONA, EMPRESA (módulo 11) Personas y empresas comparten identificador y atributos, y no tienen vida fuera del sistema

Exclusividad entre más de dos relaciones

La exclusividad puede afectar a tres o más relaciones. Por ejemplo, un PAGO se realiza con una de estas formas: tarjeta, transferencia o bizum, cada una con su entidad (TARJETA, CUENTA_BANCARIA, TELEFONO_MOVIL). Dibuja la línea discontinua entre cada par de rombos o, para no recargar el diagrama, una nota “exclusividad total entre r1, r2 y r3”.

💡 Consejo: Al pasar a tablas, la exclusividad entre relaciones 1:N se implementa muy bien con columnas de clave ajena opcionales y un CHECK de fila. Por ejemplo, para el vehículo: CHECK ((dni_persona IS NOT NULL AND cif_empresa IS NULL) OR (dni_persona IS NULL AND cif_empresa IS NOT NULL)). Lo verás en el bloque de diseño lógico.

2.3. Exclusión

📘 Concepto: Dos relaciones R1 y R2 entre las mismas entidades A y B son excluyentes si un mismo par (a, b) no puede aparecer en las dos: (a, b) ∈ R1 ⟹ (a, b) ∉ R2. Una ocurrencia de A sí puede participar en ambas relaciones, siempre que lo haga con ocurrencias distintas de B.

💻 Ejemplo: En un centro de formación, los profesores imparten cursos y también pueden recibir cursos (como alumnos). Un profesor no puede impartir y recibir el mismo curso, pero sí impartir el curso de SQL y recibir el de Python.

flowchart LR
    PROFESOR[PROFESOR] ---|"(1,n)"| imparte{"imparte<br/>N:M"}
    imparte ---|"(0,n)"| CURSO[CURSO]
    PROFESOR ---|"(0,n)"| recibe{"recibe<br/>N:M"}
    recibe ---|"(0,n)"| CURSO
    imparte -.-|"exclusión"| recibe

Ocurrencias:

Relación Profesor Curso
imparte Pérez SQL
imparte López Python
recibe Pérez Python
recibe López SQL
recibe Pérez SQL ← violación: Pérez ya imparte SQL

Diferencia con la exclusividad: si fuera exclusividad, Pérez no podría recibir ningún curso por el hecho de impartir alguno. Con exclusión, solo le está prohibido el par concreto (Pérez, SQL).

⚠️ Importante: La exclusión solo tiene sentido entre relaciones que unen las mismas entidades (o entidades de la misma jerarquía). La exclusividad, en cambio, suele darse entre relaciones que unen una entidad común con entidades distintas.

Otro ejemplo frecuente: en una liga, un EQUIPO juega un PARTIDO como local o como visitante; un mismo equipo no puede ser local y visitante del mismo partido. Es una exclusión entre las relaciones local y visitante (aunque aquí, además, cada partido tiene exactamente un local y un visitante).

2.4. Inclusividad

📘 Concepto: Una relación R2 es inclusiva respecto a otra R1 (sobre una entidad común E) si toda ocurrencia de E que participa en R2 tiene que haber participado antes (o a la vez) en R1. Se puede precisar cuántas veces con una pareja (mín, máx): para participar en R2, la ocurrencia de E debe haber participado en R1 entre mín y máx veces.

💻 Ejemplo: Para que un EMPLEADO pueda impartir cursos de formación interna, tiene que haber recibido al menos 2 cursos (cualesquiera).

flowchart LR
    EMPLEADO[EMPLEADO] ---|"(0,n)"| imparte{"imparte<br/>N:M"}
    imparte ---|"(0,n)"| CURSO[CURSO]
    EMPLEADO ---|"(0,n)"| recibe{"recibe<br/>N:M"}
    recibe ---|"(0,n)"| CURSO
    imparte -.->|"inclusividad (2,n)"| recibe

Lectura: “un empleado que participa en imparte debe participar al menos 2 veces en recibe”. No importa qué cursos recibió: puede impartir el de Excel habiendo recibido los de liderazgo y prevención de riesgos.

Otros ejemplos de inclusividad:

  • Para dirigir un proyecto, un empleado debe haber participado antes en al menos un proyecto (cualquiera).
  • Para divorciarse, una persona tiene que haber contraído matrimonio al menos una vez (ejemplo clásico de de Miguel y Piattini).
  • Para arbitrar en primera división, un árbitro debe haber arbitrado al menos 50 partidos en segunda.

2.5. Inclusión

📘 Concepto: Una relación R2 entre A y B está incluida en otra R1 entre las mismas entidades si todo par (a, b) que aparece en R2 aparece también en R1: R2 ⊆ R1.

💻 Ejemplo: Un alumno solo puede examinarse de una asignatura en la que está matriculado.

flowchart LR
    ALUMNO[ALUMNO] ---|"(0,n)"| matricula{"matricula<br/>N:M"}
    matricula ---|"(1,n)"| ASIGNATURA[ASIGNATURA]
    ALUMNO ---|"(0,n)"| examina{"se_examina<br/>N:M"}
    examina ---|"(0,n)"| ASIGNATURA
    examina -.->|"inclusión"| matricula

Ocurrencias:

Relación Alumno Asignatura ¿Válido?
matricula Ana Bases de Datos Sí
matricula Ana Programación Sí
se_examina Ana Bases de Datos Sí: el par está en matricula
se_examina Ana Redes No: Ana no está matriculada en Redes

Diferencia con la inclusividad: con inclusividad, bastaría con que Ana estuviera matriculada en alguna asignatura para examinarse de cualquiera. Con inclusión, tiene que estar matriculada en esa misma asignatura.

💡 Consejo: La inclusión tiene una implementación muy elegante en el modelo relacional: la tabla de se_examina tiene una clave ajena compuesta (alumno, asignatura) que referencia la clave primaria de la tabla de matricula. Así el SGBD garantiza la restricción sin una sola línea de código. Es una de las pocas restricciones entre relaciones que no necesita triggers.

⚠️ Importante: Cuando hay inclusión, piensa si la relación incluida no debería ser, en realidad, un atributo o una relación de la incluyente. En el ejemplo, ¿no son las convocatorias de examen algo que ocurre dentro de la matrícula? Si es así, una solución mejor es una entidad débil CONVOCATORIA dependiente de la matrícula (o una agregación de matricula). La inclusión desaparece porque queda garantizada por la estructura.

2.6. Comparación de las cuatro restricciones

  Exclusividad Exclusión Inclusividad Inclusión
Afecta a Una ocurrencia de E Un par (a, b) Una ocurrencia de E Un par (a, b)
Entidades de las relaciones Normalmente distintas Las mismas Pueden ser distintas Las mismas
Condición formal e participa en a lo sumo una Ri (a,b) ∈ R1 ⟹ (a,b) ∉ R2 e ∈ π_E(R2) ⟹ e participa en R1 entre mín y máx veces R2 ⊆ R1
Pregunta típica “¿Una cosa u otra?” “¿Lo mismo no puede estar en las dos?” “¿Para hacer esto, hay que haber hecho antes aquello?” “¿Esto concreto exige aquello mismo?”
Ejemplo Vehículo de persona o de empresa No impartir y recibir el mismo curso Impartir exige haber recibido 2 cursos Examinarse exige estar matriculado en esa asignatura
Implementación habitual CHECK sobre claves ajenas opcionales Trigger o aplicación Trigger o aplicación Clave ajena compuesta

3. Restricciones sobre una sola relación

3.1. Relaciones reflexivas

En una relación reflexiva (o recursiva), una entidad se relaciona consigo misma desempeñando dos papeles distintos (módulo 06). Casi siempre arrastran restricciones que el diagrama no recoge:

Restricción Significado Ejemplo
Irreflexividad Ninguna ocurrencia se relaciona consigo misma Un empleado no es jefe de sí mismo
Aciclicidad No hay ciclos: si A → B → C, entonces C no puede llegar a A Una categoría no puede ser “abuela” de sí misma
Asimetría Si (a, b) está, (b, a) no puede estar Si A es jefe de B, B no es jefe de A
Simetría Si (a, b) está, (b, a) también se considera La amistad en una red social
Profundidad máxima Número máximo de niveles Como mucho 4 niveles de categorías
flowchart LR
    classDef nota fill:#fff8dc,stroke:#999,stroke-dasharray: 3 3
    CATEGORIA[CATEGORIA] ---|"(0,1) padre"| contiene{"contiene<br/>1:N"}
    contiene ---|"(0,n) hija"| CATEGORIA
    CATEGORIA --- c_id([<u>cod_categoria</u>])
    CATEGORIA --- c_nom([nombre])
    R3["R3: la jerarquía de categorías es acíclica y tiene como máximo 4 niveles"]:::nota
    R3 -.- contiene

⚠️ Importante: La simetría merece una decisión explícita en el diccionario. Si “Ana es amiga de Luis” implica “Luis es amigo de Ana”, ¿guardas el par una vez o dos? Si lo guardas una vez, documenta una regla de orden (por ejemplo, “el primer usuario es el de menor identificador”) para evitar que el mismo hecho aparezca como (Ana, Luis) y (Luis, Ana).

3.2. Varias relaciones entre las mismas entidades con papeles distintos

Cuando dos entidades están unidas por varias relaciones (o una entidad está unida dos veces a otra con papeles distintos), suelen aparecer restricciones de distinción de papeles.

💻 Ejemplo: Un PARTIDO tiene un equipo local y un equipo visitante.

flowchart LR
    classDef nota fill:#fff8dc,stroke:#999,stroke-dasharray: 3 3
    EQUIPO[EQUIPO] ---|"(1,1) local"| local{"juega_local<br/>1:N"}
    local ---|"(0,n)"| PARTIDO[PARTIDO]
    EQUIPO ---|"(1,1) visitante"| visit{"juega_visitante<br/>1:N"}
    visit ---|"(0,n)"| PARTIDO
    local -.-|"exclusión"| visit
    R4["R4: en una misma jornada un equipo juega como mucho un partido"]:::nota
    R4 -.- PARTIDO
  • La exclusión entre juega_local y juega_visitante garantiza que el local y el visitante de un partido son equipos distintos.
  • La nota R4 es una restricción entre ocurrencias de PARTIDO que ninguna notación puede expresar.

3.3. Cardinalidades condicionadas

Las cardinalidades del diagrama son fijas. Cuando el máximo o el mínimo depende de otro dato, hay que anotarlo:

  • “Un alumno de primer curso se matricula de 1 a 6 asignaturas; los de cursos superiores, de 1 a 10.”
  • “Una habitación doble aloja como máximo 2 huéspedes por reserva; una suite, 4.”
  • “Un socio premium puede tener 5 películas alquiladas a la vez; uno básico, 2.”

En el diagrama se dibuja la cardinalidad más amplia (la que admite todos los casos: (1,10), (1,4), (0,5)…) y la condición se escribe como restricción. Si la diferencia está ligada a un subtipo con atributos propios, a veces conviene crear una jerarquía y unir la relación con cardinalidades distintas a cada subtipo (módulo 11).

💡 Consejo: Observa además que “tener 5 películas alquiladas a la vez” no es una cardinalidad de la relación alquila en sentido estricto, porque un socio puede alquilar cientos de películas a lo largo del tiempo. Es una restricción sobre las ocurrencias vigentes (sin devolver). Las restricciones “a la vez” casi siempre son temporales: lo verás en la sección 7.

3.4. Restricciones que afectan a caminos

Algunas reglas exigen que dos caminos distintos del diagrama lleven al mismo sitio:

💻 Ejemplo: “Un médico solo puede atender consultas de pacientes asignados a su mismo centro de salud.” Hay dos caminos de CONSULTA a CENTRO: CONSULTA → MEDICO → CENTRO y CONSULTA → PACIENTE → CENTRO. La restricción exige que ambos caminos den el mismo centro.

Estas restricciones de “caminos coincidentes” aparecen en los ciclos del diagrama y las estudiamos con más detalle en la sección 6.


4. Relaciones de grado superior

Muchas de las restricciones de este módulo aparecen también en las relaciones ternarias y n-arias, en las que un hecho involucra simultáneamente a tres o más entidades (un proveedor suministra una pieza a un proyecto; un profesor imparte una asignatura a un grupo en un aula). En ellas, el cálculo de cardinalidades se hace fijando pares de ocurrencias, y su sustitución por varias relaciones binarias puede perder información.

💡 Consejo: Todo lo relativo a las relaciones ternarias y n-arias (cuándo son necesarias, cómo calcular sus cardinalidades, cuándo pueden descomponerse en binarias y cómo transformarlas en entidad) se estudia en el módulo de relaciones ternarias. En este módulo solo las usaremos puntualmente, en el apartado de las relaciones con repetición (sección 8).


5. Trampas de conexión

Las trampas de conexión (connection traps), descritas por Codd y popularizadas por Connolly y Begg, son defectos de diseño en los que el diagrama parece permitir responder a una pregunta, pero en realidad la información necesaria no está o está mezclada. Son muy traicioneras porque el diagrama es sintácticamente correcto y las cardinalidades pueden ser exactas: el problema está en dónde se ha colocado cada relación.

5.1. Trampa de abanico (fan trap)

📘 Concepto: Hay una trampa de abanico cuando de una misma entidad salen dos o más relaciones 1:N (en “abanico”) y se pretende deducir la relación entre las entidades del otro extremo. El camino entre ellas es ambiguo.

💻 Ejemplo: Una empresa se organiza en divisiones. Cada división opera varias sucursales y tiene contratados varios empleados. Se ha modelado así:

flowchart LR
    SUCURSAL[SUCURSAL] ---|"(1,n)"| opera{"opera<br/>1:N"}
    opera ---|"(1,1)"| DIVISION[DIVISION]
    DIVISION ---|"(1,1)"| tiene{"tiene<br/>1:N"}
    tiene ---|"(1,n)"| EMPLEADO[EMPLEADO]

Lectura: cada sucursal pertenece a una división; cada empleado pertenece a una división. Todo correcto… hasta que alguien pregunta: “¿en qué sucursal trabaja el empleado Ruiz?”

Ocurrencias:

opera (sucursal → división) tiene (empleado → división)
Sucursal Madrid → División Norte Ruiz → División Norte
Sucursal Burgos → División Norte Gómez → División Norte
Sucursal Sevilla → División Sur Díaz → División Sur

Desde Ruiz llegamos a la División Norte, y desde ella a dos sucursales: Madrid y Burgos. Es imposible saber en cuál trabaja Ruiz. La información no existe en el modelo.

Solución: reestructurar para que el camino sea el correcto. Si cada empleado trabaja en una sucursal y cada sucursal pertenece a una división, la relación debe ir de la sucursal al empleado:

flowchart LR
    DIVISION[DIVISION] ---|"(1,1)"| opera{"opera<br/>1:N"}
    opera ---|"(1,n)"| SUCURSAL[SUCURSAL]
    SUCURSAL ---|"(1,1)"| trabaja{"trabaja<br/>1:N"}
    trabaja ---|"(1,n)"| EMPLEADO[EMPLEADO]

Ahora desde Ruiz llegamos a una única sucursal (Madrid), y desde ella a una única división: la pregunta “¿en qué división trabaja Ruiz?” sigue teniendo respuesta, por composición de relaciones.

⚠️ Importante: Si hay empleados que dependen directamente de la división sin sucursal (por ejemplo, el personal de la sede de la división), la solución anterior no basta: habría que mantener también una relación DIVISION–EMPLEADO y una exclusividad entre trabaja y esa relación, o hacer trabaja opcional y anotar una restricción. La reestructuración siempre debe partir de la realidad, no de una receta.

Cómo detectarla: busca entidades de las que salgan dos o más relaciones con cardinalidad máxima n hacia el otro lado y pregúntate: “¿necesito saber cómo se relacionan entre sí las entidades del otro extremo?”. Si la respuesta es sí, tienes un abanico.

5.2. Trampa de sima o de abismo (chasm trap)

📘 Concepto: Hay una trampa de sima (o de abismo) cuando el modelo sugiere que existe un camino entre dos entidades, pero ese camino pasa por una relación de participación opcional (mínima 0), de modo que para algunas ocurrencias el camino se interrumpe y la información se pierde.

💻 Ejemplo: Una inmobiliaria tiene oficinas; cada oficina tiene empleados, y algunos empleados supervisan inmuebles en alquiler. Cada inmueble lo ofrece una oficina, pero puede no tener todavía un empleado supervisor asignado. Se ha modelado así:

flowchart LR
    OFICINA[OFICINA] ---|"(1,1)"| tiene{"tiene<br/>1:N"}
    tiene ---|"(1,n)"| EMPLEADO[EMPLEADO]
    EMPLEADO ---|"(0,1)"| supervisa{"supervisa<br/>1:N"}
    supervisa ---|"(0,n)"| INMUEBLE[INMUEBLE]

Lectura: cada empleado está en una oficina; cada empleado supervisa de 0 a n inmuebles; cada inmueble tiene 0 o 1 supervisor. Pregunta: “¿qué oficina ofrece el inmueble de la calle Mayor, 5?”

Ocurrencias:

Inmueble Supervisor Oficina del supervisor
C/ Mayor, 3 Pérez Oficina Centro
C/ Mayor, 5 — ¿?
Av. Libertad, 12 López Oficina Norte

Para los inmuebles sin supervisor, el camino INMUEBLE → EMPLEADO → OFICINA se rompe: hay una “sima” en medio. El enunciado dice que todo inmueble lo ofrece una oficina, pero el modelo no puede decirnos cuál.

Solución: añadir la relación que falta directamente entre las entidades:

flowchart LR
    OFICINA[OFICINA] ---|"(1,1)"| tiene{"tiene<br/>1:N"}
    tiene ---|"(1,n)"| EMPLEADO[EMPLEADO]
    EMPLEADO ---|"(0,1)"| supervisa{"supervisa<br/>1:N"}
    supervisa ---|"(0,n)"| INMUEBLE[INMUEBLE]
    OFICINA ---|"(1,1)"| ofrece{"ofrece<br/>1:N"}
    ofrece ---|"(0,n)"| INMUEBLE

Ahora el modelo tiene un ciclo (OFICINA–EMPLEADO–INMUEBLE–OFICINA) y aparece una restricción nueva que hay que anotar: “si un inmueble tiene supervisor, el supervisor pertenece a la misma oficina que ofrece el inmueble” (si esa es la regla del negocio). Es una restricción de caminos coincidentes (sección 3.4).

Cómo detectarla: busca caminos que usas para responder preguntas importantes y comprueba si alguna relación del camino tiene mínima 0 en el sentido en que lo recorres. Si es así, pregúntate: “¿qué pasa con las ocurrencias que no participan?”.

5.3. Comparación

  Trampa de abanico Trampa de sima
Síntoma El camino existe pero es ambiguo (lleva a varias ocurrencias) El camino se interrumpe para algunas ocurrencias
Causa Dos relaciones 1:N que salen de la misma entidad Una relación con participación opcional en medio del camino
Se detecta con la pregunta “¿A cuál de todas?” “¿Y los que no tienen…?”
Solución Reestructurar las relaciones para que el camino sea el correcto Añadir la relación directa que falta
Efecto secundario Desaparece una relación y se crea otra Aparece un ciclo, normalmente con una restricción de consistencia

💡 Consejo: Las trampas se descubren haciendo preguntas al diagrama. Al terminar un diseño, toma la lista de consultas que el usuario necesita (“listado de empleados por sucursal”, “inmuebles de cada oficina”) e intenta responder cada una recorriendo el diagrama con el dedo. Si alguna pregunta tiene varias respuestas o ninguna, has encontrado una trampa.


6. Redundancia de relaciones y ciclos

6.1. Ciclos en el diagrama

📘 Concepto: Un ciclo es un camino cerrado de relaciones que empieza y termina en la misma entidad. Una relación de un ciclo es redundante si su contenido puede deducirse por composición de las demás relaciones del ciclo, para todas las ocurrencias y en todo momento.

Los ciclos no son malos en sí: muchos son necesarios. Lo que hay que hacer con cada ciclo es analizarlo para decidir entre tres posibilidades:

  1. Una relación es redundante → se elimina (o se mantiene como redundancia controlada y documentada).
  2. Ninguna es redundante, pero los caminos deben coincidir → se mantiene el ciclo y se anota una restricción.
  3. Ninguna es redundante y los caminos son independientes → se mantiene el ciclo sin más.

6.2. Ejemplo de relación redundante

💻 Ejemplo: En un instituto, cada alumno pertenece a un grupo (1ºA, 1ºB…), cada grupo pertenece a un curso (1º de ESO, 2º de ESO…), y además alguien ha dibujado que el alumno “está en” un curso.

flowchart LR
    ALUMNO[ALUMNO] ---|"(0,n)"| pertenece{"pertenece<br/>1:N"}
    pertenece ---|"(1,1)"| GRUPO[GRUPO]
    GRUPO ---|"(1,n)"| es_de{"es_de<br/>1:N"}
    es_de ---|"(1,1)"| CURSO[CURSO]
    ALUMNO ---|"(0,n)"| esta_en{"esta_en<br/>1:N"}
    esta_en ---|"(1,1)"| CURSO

Si cada alumno está en exactamente un grupo y cada grupo es de exactamente un curso, el curso del alumno se deduce siempre: alumno → grupo → curso. La relación esta_en es redundante:

  • Ocupa espacio sin aportar información.
  • Puede llevar a inconsistencias: que Ana esté en el grupo 1ºA (de 1º de ESO) y esta_en diga 2º de ESO.

Solución: eliminar esta_en.

6.3. Ejemplo de ciclo no redundante

💻 Ejemplo: En una universidad, cada profesor está adscrito a un departamento, cada asignatura pertenece a un departamento y cada profesor imparte asignaturas.

flowchart LR
    PROFESOR[PROFESOR] ---|"(0,n)"| adscrito{"adscrito<br/>1:N"}
    adscrito ---|"(1,1)"| DEPARTAMENTO[DEPARTAMENTO]
    ASIGNATURA[ASIGNATURA] ---|"(1,n)"| pertenece{"pertenece<br/>1:N"}
    pertenece ---|"(1,1)"| DEPARTAMENTO
    PROFESOR ---|"(1,n)"| imparte{"imparte<br/>N:M"}
    imparte ---|"(0,n)"| ASIGNATURA

¿Es redundante alguna relación? Analicemos:

  • ¿Se deduce imparte de las otras dos? No: saber que el profesor y la asignatura son del mismo departamento no dice qué asignaturas imparte cada profesor.
  • ¿Se deduce adscrito? No: un profesor puede impartir asignaturas de varios departamentos (por ejemplo, un profesor de Matemáticas que da Estadística en el grado de Economía), y entonces no sabríamos a cuál está adscrito.
  • ¿Se deduce pertenece? No, por la misma razón.

Ninguna es redundante. Pero, si el negocio dijera “un profesor solo puede impartir asignaturas de su departamento”, el ciclo tendría una restricción de caminos coincidentes que habría que anotar.

6.4. Procedimiento para analizar un ciclo

Para cada ciclo del diagrama, y para cada relación R del ciclo:

  1. Elimina mentalmente R y recorre el resto del ciclo entre las dos entidades que R unía.
  2. Si el camino alternativo devuelve exactamente las mismas parejas que R, para todas las ocurrencias, R es redundante. Para ello normalmente hace falta que las relaciones del camino alternativo tengan máximo 1 en el sentido del recorrido (si alguna tiene máximo n, el camino devuelve varias ocurrencias y no puede sustituir a R).
  3. Si el camino devuelve un superconjunto de R (más parejas), R no es redundante, pero hay una restricción de inclusión implícita: R ⊆ composición del camino. Anótala si es una regla del negocio.
  4. Si el camino puede devolver parejas que no están en R y R puede tener parejas que no devuelve el camino, el ciclo es independiente.
  5. Comprueba también la dimensión temporal: dos relaciones pueden coincidir hoy y dejar de hacerlo mañana (ver 6.5).

6.5. Una redundancia aparente: el tiempo

💻 Ejemplo: En una tienda, cada PEDIDO lo hace un CLIENTE, y cada cliente tiene asignado un COMERCIAL. Además, cada pedido registra el comercial que lo gestionó. ¿Es redundante pedido → comercial, ya que se puede obtener por pedido → cliente → comercial?

No lo es, por dos motivos:

  • El comercial asignado a un cliente cambia con el tiempo. Si en 2026 Ana atendía al cliente y en 2027 pasa a atenderlo Luis, los pedidos de 2026 deben seguir figurando como gestionados por Ana.
  • Puede que un pedido lo gestione otro comercial (por vacaciones del asignado).

Es exactamente lo que ocurre en el esquema EMPRESA de este curso: CLIENTES tiene comercial_id y PEDIDOS tiene empleado_id, y no son redundantes (lo verás en el caso EMPRESA del módulo 15).

6.6. Cuándo es aceptable una redundancia

A veces se mantiene una relación o un atributo redundante a propósito. Se habla entonces de redundancia controlada, y es aceptable si se cumplen todas estas condiciones:

Condición Explicación
Está justificada Por rendimiento (evitar recorrer un camino largo en consultas muy frecuentes) o por claridad para el usuario
Está documentada En el diccionario figura qué elemento es redundante y de qué se deduce
Hay un mecanismo de consistencia Una restricción que garantice que el dato redundante coincide con el deducido (trigger, proceso de la aplicación, vista materializada)
Se ha valorado el coste Cada modificación del dato original obliga a actualizar el redundante

⚠️ Importante: En el modelo conceptual, la regla general es no incluir redundancias: el modelo conceptual describe la realidad, no el rendimiento. Las redundancias controladas por rendimiento se introducen más tarde, en el diseño lógico o físico (desnormalización, módulo 20). Si las dibujas en el modelo conceptual, márcalas y justifícalas.


7. La dimensión temporal

7.1. Instantánea o historia

Casi todas las bases de datos tienen que decidir, para cada dato que cambia, si les interesa solo su valor actual (una instantánea) o todos los valores que ha tenido (su historia). El modelo E/R básico no tiene ningún mecanismo especial para el tiempo: todo lo que tenga que ver con la historia hay que modelarlo explícitamente.

Necesidad Ejemplo Modelado
Solo valor actual Teléfono de un cliente Atributo simple
Valor en un momento concreto, congelado Precio cobrado en una venta Atributo de la relación o de la entidad débil (línea de pedido)
Historia completa de un atributo Evolución del salario de un empleado Entidad de historial débil
Historia de una relación Departamentos por los que ha pasado un empleado Relación con fechas convertida en entidad
Estado actual + historia de estados Estados de un pedido Atributo estado + entidad CAMBIO_ESTADO

💡 Consejo: Pregunta siempre en las entrevistas: “¿y si este dato cambia, os interesa saber el valor anterior?”. Es una de las preguntas que más modifican un diseño, y casi nunca la responde el enunciado de forma explícita.

7.2. Atributos históricos

💻 Ejemplo: Interesa conocer la evolución del salario de cada empleado: cuánto cobraba en cada periodo.

Un atributo multivaluado compuesto (salario_historico con importe y fecha_desde) sería válido en el modelo conceptual, pero es más claro y más fácil de transformar modelarlo como entidad débil:

flowchart LR
    classDef debil stroke-width:4px
    EMPLEADO[EMPLEADO] --- e_id([<u>num_empleado</u>])
    EMPLEADO --- e_nom([nombre])
    EMPLEADO --- e_sal([salario_actual])
    EMPLEADO ---|"(1,1)"| tiene{"tiene<br/>ID"}:::debil
    tiene ---|"(0,n)"| HSAL[HISTORICO_SALARIO]:::debil
    HSAL --- h_ini(["<u>fecha_desde</u> parcial"])
    HSAL --- h_fin(["fecha_hasta (0,1)"])
    HSAL --- h_imp([importe])
  • HISTORICO_SALARIO se identifica por el empleado + fecha_desde (identificador parcial).
  • fecha_hasta es opcional: el periodo vigente no tiene fin.
  • salario_actual en EMPLEADO es redundante con el registro vigente del historial. Puedes eliminarlo (y obtener el salario actual buscando el periodo sin fecha_hasta) o mantenerlo como redundancia controlada documentada.

Restricciones que hay que anotar:

  • R1: fecha_hasta ≥ fecha_desde.
  • R2: los periodos de un mismo empleado no se solapan.
  • R3: como mucho un periodo vigente (sin fecha_hasta) por empleado.
  • R4 (opcional): los periodos son contiguos, sin huecos (cada fecha_desde es el día siguiente al fecha_hasta anterior).

⚠️ Importante: Las restricciones de no solapamiento de periodos (R2) son de las más frecuentes en las bases de datos reales, y no pueden implementarse con un CHECK porque comparan varias filas. Requieren triggers o lógica de aplicación.

7.3. De relación actual a relación histórica

Este es uno de los cambios más importantes que introduce el tiempo: las cardinalidades cambian.

💻 Ejemplo: “Cada empleado trabaja en un departamento.” Si solo interesa el departamento actual, la relación es 1:N:

flowchart LR
    EMPLEADO[EMPLEADO] ---|"(1,n)"| trabaja{"trabaja<br/>1:N"}
    trabaja ---|"(1,1)"| DEPARTAMENTO[DEPARTAMENTO]

Si interesa la historia (“en qué departamentos ha trabajado y cuándo”), un empleado ha podido estar en muchos departamentos a lo largo del tiempo, y la relación pasa a ser N:M con atributos de periodo:

flowchart LR
    EMPLEADO[EMPLEADO] ---|"(0,n)"| trabajo{"ha_trabajado<br/>N:M"}
    trabajo ---|"(1,n)"| DEPARTAMENTO[DEPARTAMENTO]
    trabajo --- t_ini([fecha_inicio])
    trabajo --- t_fin(["fecha_fin (0,1)"])

Pero cuidado: con esta relación N:M, cada par (empleado, departamento) solo puede aparecer una vez. Si Ana estuvo en Ventas de 2018 a 2020, pasó a Marketing y volvió a Ventas en 2023, no podemos registrar las dos estancias en Ventas. Es el problema de las relaciones con repetición, que resolvemos en la sección 8 convirtiendo la relación en entidad:

flowchart LR
    classDef debil stroke-width:4px
    EMPLEADO[EMPLEADO] ---|"(1,1)"| tiene{"tiene<br/>ID"}:::debil
    tiene ---|"(1,n)"| ESTANCIA[ESTANCIA]:::debil
    ESTANCIA --- es_ini(["<u>fecha_inicio</u> parcial"])
    ESTANCIA --- es_fin(["fecha_fin (0,1)"])
    ESTANCIA ---|"(0,n)"| en{"en<br/>1:N"}
    en ---|"(1,1)"| DEPARTAMENTO[DEPARTAMENTO]

Ahora cada estancia es una ocurrencia distinta, identificada por el empleado y su fecha de inicio, y Ana puede tener dos estancias en Ventas.

💡 Consejo: Muchas veces el sistema necesita las dos cosas: consultar rápidamente el departamento actual y conservar la historia. Una solución habitual es mantener la relación actual 1:N (trabaja) y la entidad ESTANCIA, documentando la restricción de consistencia: “el departamento de trabaja coincide con el de la estancia vigente”.

7.4. Congelar el valor en el momento de la transacción

Hay datos que no queremos “historiar” en general, pero sí congelar en el momento en que se usan. El caso típico es el precio:

  • El precio de un PRODUCTO es el precio actual de catálogo.
  • El precio_unitario de una LINEA_PEDIDO es el precio al que se vendió, y no debe cambiar si mañana sube el catálogo.

Ambos atributos no son redundantes, aunque el día de la venta coincidan. Es exactamente el diseño del esquema EMPRESA (productos.precio y lineas_pedido.precio_unitario).

⚠️ Importante: Un error frecuente es eliminar el precio de la línea de pedido “porque ya está en el producto”. Si lo haces, al subir los precios cambiarían los importes de todas las facturas antiguas.

7.5. Tiempo válido y tiempo de transacción

En aplicaciones avanzadas (seguros, banca, sanidad) se distinguen dos tiempos:

  • Tiempo válido (valid time): cuándo es cierto el hecho en la realidad (“Ana trabaja en Ventas desde el 1 de marzo”).
  • Tiempo de transacción (transaction time): cuándo se registró en la base de datos (“lo grabamos el 15 de marzo”).

Una base de datos que registra ambos se llama bitemporal. En el modelo conceptual, si te piden los dos, modélalos como atributos explícitos (fecha_inicio_validez, fecha_fin_validez, fecha_registro, fecha_anulacion). Algunos SGBD ofrecen mecanismos específicos: Oracle, por ejemplo, permite declarar periodos de validez sobre una tabla (Temporal Validity) y consultar datos pasados con Flashback Query, pero son detalles de implementación que no cambian el modelo conceptual.


8. Relaciones N:M que deben admitir repetición

8.1. El problema

💻 Ejemplo: En un videoclub (o en una plataforma de alquiler digital), un socio alquila películas. Un socio puede alquilar muchas películas, una película puede alquilarse a muchos socios, y un mismo socio puede alquilar la misma película varias veces en fechas distintas.

El primer impulso es una relación N:M con la fecha como atributo:

flowchart LR
    SOCIO[SOCIO] ---|"(0,n)"| alquila{"alquila<br/>N:M"}
    alquila ---|"(0,n)"| PELICULA[PELICULA]
    alquila --- a_fec([fecha])
    alquila --- a_dev(["fecha_devolucion (0,1)"])
    SOCIO --- s_id([<u>num_socio</u>])
    PELICULA --- p_id([<u>cod_pelicula</u>])

Pero, por definición, una ocurrencia de una relación está determinada por las ocurrencias de las entidades que relaciona: en una relación binaria, cada par (socio, película) aparece como mucho una vez. Si Ana alquila Casablanca el 3 de marzo y otra vez el 20 de junio, el segundo alquiler no se puede registrar (o machacaría al primero).

Socio Película Fecha  
Ana Casablanca 03/03/2026  
Ana Casablanca 20/06/2026 ← no admitido: el par (Ana, Casablanca) ya existe

⚠️ Importante: El atributo fecha de la relación no forma parte de su identificación. Por eso no basta con ponerlo: la relación sigue identificada solo por (socio, película). Al pasar a tablas, la clave primaria sería (num_socio, cod_pelicula) y rechazaría el segundo alquiler.

8.2. Solución 1: convertir la relación en entidad

La solución más clara y la más usada es reificar la relación: convertir alquila en una entidad ALQUILER unida a SOCIO y a PELICULA con dos relaciones 1:N. Cada alquiler es una ocurrencia distinta, aunque se repita el par.

Con identificador propio (entidad fuerte):

flowchart LR
    SOCIO[SOCIO] ---|"(1,1)"| realiza{"realiza<br/>1:N"}
    realiza ---|"(0,n)"| ALQUILER[ALQUILER]
    ALQUILER ---|"(0,n)"| de{"de<br/>1:N"}
    de ---|"(1,1)"| PELICULA[PELICULA]
    ALQUILER --- al_id([<u>num_alquiler</u>])
    ALQUILER --- al_fec([fecha_alquiler])
    ALQUILER --- al_dev(["fecha_devolucion (0,1)"])
    ALQUILER --- al_imp([importe])

Como entidad débil de SOCIO, identificada por el socio + la película + la fecha (si no puede haber dos alquileres del mismo socio y película el mismo día):

flowchart LR
    classDef debil stroke-width:4px
    SOCIO[SOCIO] ---|"(1,1)"| realiza{"realiza<br/>ID"}:::debil
    realiza ---|"(0,n)"| ALQUILER[ALQUILER]:::debil
    ALQUILER ---|"(0,n)"| de{"de<br/>1:N"}
    de ---|"(1,1)"| PELICULA[PELICULA]
    ALQUILER --- al_fec(["<u>fecha_alquiler</u> parcial"])
    ALQUILER --- al_dev(["fecha_devolucion (0,1)"])

En esta segunda versión, la película también forma parte de la identificación (la identificación completa sería socio + película + fecha). Algunos autores lo indican haciendo que de sea también relación de identificación; en cualquier caso, documéntalo en el diccionario.

Opción Ventajas Inconvenientes
Entidad fuerte con num_alquiler Simple; admite cualquier repetición, incluso el mismo día; fácil de referenciar desde otras entidades (pagos, incidencias) El identificador es artificial; hay que añadir una restricción si no se quieren duplicados reales
Entidad débil con fecha El identificador refleja la realidad Identificador largo; problemas si se puede repetir el mismo día

8.3. Solución 2: añadir el tiempo como entidad (ternaria)

Otra solución clásica es considerar la FECHA como una entidad más y usar una relación ternaria SOCIO–PELICULA–FECHA. Cada ocurrencia de la relación queda determinada por el trío (socio, película, fecha), así que el par puede repetirse con fechas distintas. Esta solución es correcta desde el punto de vista formal y aparece en muchos libros, pero en la práctica se prefiere la reificación, porque la entidad FECHA no tiene atributos propios de interés y complica el diagrama. El cálculo de las cardinalidades de esta ternaria se explica en el módulo de relaciones ternarias.

8.4. Una pregunta previa: ¿qué se alquila exactamente?

En un videoclub físico, lo que se alquila no es la película (la obra), sino una copia concreta (el DVD número 3 de Casablanca). Si el enunciado habla de copias, inventario o estado de los soportes, el diseño correcto es:

flowchart LR
    classDef debil stroke-width:4px
    PELICULA[PELICULA] ---|"(1,1)"| tiene{"tiene<br/>ID"}:::debil
    tiene ---|"(1,n)"| COPIA[COPIA]:::debil
    COPIA --- co_num(["<u>num_copia</u> parcial"])
    COPIA --- co_est([estado])
    SOCIO[SOCIO] ---|"(1,1)"| realiza{"realiza<br/>1:N"}
    realiza ---|"(0,n)"| ALQUILER[ALQUILER]
    ALQUILER ---|"(0,n)"| de{"de<br/>1:N"}
    de ---|"(1,1)"| COPIA
    ALQUILER --- al_id([<u>num_alquiler</u>])
    ALQUILER --- al_fec([fecha_alquiler])
    ALQUILER --- al_dev(["fecha_devolucion (0,1)"])

Con restricciones como:

  • R1: una copia no puede estar en dos alquileres sin devolver a la vez.
  • R2: un socio no puede tener más de 3 alquileres sin devolver a la vez.
  • R3: fecha_devolucion ≥ fecha_alquiler.

8.5. Cómo reconocer una relación con repetición

Hazte estas preguntas para cada relación N:M:

  1. ¿Puede repetirse el mismo par de ocurrencias en momentos distintos? (alquilar, comprar, visitar, consultar, examinarse, jugar…)
  2. ¿El hecho tiene fecha y es un acontecimiento más que un vínculo estable?
  3. ¿Interesa referenciar el hecho desde otras entidades (pagar un alquiler, reclamar una consulta)?

Si la respuesta a la 1 es sí, necesitas reificar (o una ternaria con el tiempo). Si las respuestas a la 2 o la 3 son sí, probablemente te convenga reificar aunque no haya repetición.

Verbo del enunciado Normalmente es Ejemplo
“pertenece”, “trabaja en”, “está matriculado” (sin historia) Vínculo estable → relación Alumno matriculado en asignatura (un curso académico)
“alquila”, “compra”, “visita”, “consulta”, “reserva”, “juega” Acontecimiento repetible → entidad Alquiler, compra, visita, cita médica, reserva, partido
“ha trabajado en”, “ha vivido en” (con historia) Vínculo con historia → entidad Estancia, periodo de residencia

9. Atributos derivados y redundancia controlada

9.1. Qué es un atributo derivado

📘 Concepto: Un atributo derivado es aquel cuyo valor puede calcularse a partir de otros datos del modelo. Se dibuja con borde discontinuo y debe llevar documentada su regla de derivación.

Origen del cálculo Ejemplo Regla de derivación
Atributos de la misma ocurrencia edad de una persona Años completos entre fecha_nacimiento y hoy
Atributos de la misma ocurrencia importe de una línea de pedido cantidad × precio_unitario × (1 − descuento / 100)
Ocurrencias relacionadas (agregado) num_empleados de un departamento Número de empleados relacionados por trabaja
Ocurrencias relacionadas (agregado) importe_total de un pedido Suma del importe de sus líneas
Ocurrencias relacionadas (valor) ultima_fecha_compra de un cliente Máxima fecha de sus pedidos
El tiempo antiguedad de un empleado Años desde fecha_alta
flowchart LR
    classDef debil stroke-width:4px
    classDef derivado stroke-dasharray: 5 5
    PEDIDO[PEDIDO] --- p_id([<u>num_pedido</u>])
    PEDIDO --- p_fec([fecha])
    PEDIDO --- p_tot([importe_total]):::derivado
    PEDIDO ---|"(1,1)"| contiene{"contiene<br/>ID"}:::debil
    contiene ---|"(1,n)"| LINEA[LINEA_PEDIDO]:::debil
    LINEA --- l_num(["<u>num_linea</u> parcial"])
    LINEA --- l_can([cantidad])
    LINEA --- l_pre([precio_unitario])
    LINEA --- l_des([descuento])
    LINEA --- l_imp([importe]):::derivado

9.2. ¿Se dibuja o no?

En el modelo conceptual, incluir un atributo derivado es una decisión de documentación, no de almacenamiento: dibujarlo dice “este dato es importante para el usuario y se calcula así”. En los diseños lógico y físico se decide si se almacena o se calcula cada vez:

Criterio Calcular cada vez Almacenar
Frecuencia de consulta Baja Muy alta
Coste del cálculo Bajo (operación sobre la misma fila) Alto (agregados sobre muchas filas)
Frecuencia de cambio de los datos de origen Alta Baja
Riesgo de inconsistencia Ninguno Hay que mantenerlo sincronizado
Datos que dependen de “hoy” (edad, antigüedad) Casi siempre calcular Nunca almacenar (caducan solos)

💡 Consejo: En Oracle, un derivado de la misma fila puede implementarse sin redundancia como columna virtual (importe GENERATED ALWAYS AS (cantidad * precio_unitario * (1 - descuento / 100)) VIRTUAL) o en una vista. Los agregados se resuelven con vistas, vistas materializadas o triggers. Lo verás en los módulos de DDL.

9.3. Redundancia que no es derivación

No confundas un atributo derivado con uno que coincide con otro en un momento dado pero tiene significado distinto. Ya hemos visto dos casos:

  • precio_unitario de la línea frente a precio del producto (sección 7.4): no es derivado.
  • Comercial que gestionó el pedido frente a comercial asignado al cliente (sección 6.5): no es derivado.

La pregunta que lo distingue es: “¿si cambia el dato de origen, debe cambiar este?”. Si la respuesta es sí, es derivado. Si es no, es un dato independiente.


10. Restricciones de integridad de negocio

10.1. El catálogo de restricciones

Al terminar el diseño conceptual, todas las restricciones que no estén en el diagrama deben quedar recogidas en un catálogo, con un identificador, un enunciado en lenguaje natural, los elementos afectados y, si ya se conoce, la forma prevista de implementación. Este catálogo forma parte del diccionario de datos (módulo 14).

Reglas para redactar bien una restricción:

  1. Atómica: una restricción, una idea. “El precio es positivo y el stock no es negativo” son dos restricciones.
  2. Verificable: debe poder comprobarse sobre los datos. Evita “razonable”, “adecuado”, “normalmente”.
  3. Con los nombres del modelo: usa los nombres exactos de entidades, atributos y relaciones del diagrama.
  4. Sin decidir la implementación: “el saldo no puede ser negativo”, no “poner un CHECK en la tabla de cuentas”. La implementación es una columna aparte.
  5. Con su origen: de qué requisito o entrevista procede, para poder validarla con el usuario.

Una plantilla útil: “[Elemento] [debe / no puede] [condición] [cuando / salvo que …]”.

10.2. Ejemplo completo: catálogo de un videoclub

Para el modelo de la sección 8.4 (película, copia, socio, alquiler) más una entidad TARIFA:

Id Restricción Elementos Tipo Implementación prevista
R1 El año de una película está entre 1888 y el año actual PELICULA.anio Dominio CHECK (con límite fijo) o aplicación
R2 El estado de una copia es ‘BUENO’, ‘DAÑADO’ o ‘BAJA’ COPIA.estado Dominio CHECK ... IN (...)
R3 El email de un socio es único SOCIO.email Unicidad UNIQUE
R4 fecha_devolucion ≥ fecha_alquiler ALQUILER Entre atributos CHECK de tabla
R5 El importe de un alquiler es mayor que 0 ALQUILER.importe Dominio CHECK
R6 Una copia en estado ‘BAJA’ no puede alquilarse ALQUILER, COPIA.estado Entre entidades Trigger o aplicación
R7 Una copia no puede estar en dos alquileres sin devolver a la vez ALQUILER Entre ocurrencias Trigger o aplicación (o índice único basado en función)
R8 Un socio no puede tener más de 3 alquileres sin devolver ALQUILER, SOCIO Cardinalidad condicionada Trigger o aplicación
R9 Un socio con alquileres con más de 30 días de retraso no puede alquilar ALQUILER, SOCIO Negocio Aplicación
R10 El importe de un alquiler es el de la tarifa vigente en su fecha ALQUILER, TARIFA Derivación / temporal Aplicación (y conservar el importe en el alquiler)
R11 Los periodos de vigencia de las tarifas no se solapan TARIFA Entre ocurrencias / temporal Trigger o aplicación
R12 Una película no puede borrarse si tiene alquileres PELICULA, ALQUILER Referencial Clave ajena sin ON DELETE CASCADE

💡 Consejo: Fíjate en la última columna: solo una parte de las restricciones se implementa de forma declarativa (CHECK, UNIQUE, NOT NULL, FOREIGN KEY). El resto requiere triggers (que se estudian en el Curso de Oracle PL/SQL) o lógica en la aplicación. El estándar SQL define además las aserciones (CREATE ASSERTION), restricciones generales sobre varias tablas, pero tradicionalmente los SGBD comerciales no las han implementado; comprueba la documentación de tu versión antes de contar con ellas.

10.3. Clasificación según la implementación

Mecanismo Qué puede garantizar Ejemplos del catálogo
Tipo de dato y NOT NULL Dominio básico y obligatoriedad —
CHECK de columna o de tabla Condiciones sobre una sola fila R2, R4, R5
UNIQUE / PRIMARY KEY Unicidad de valores R3
FOREIGN KEY y acciones referenciales Existencia de la ocurrencia referenciada; comportamiento al borrar R12
Índice único sobre expresión Unicidad condicionada (“solo un alquiler abierto por copia”) R7 (técnica avanzada)
Trigger Condiciones sobre varias filas o tablas, transiciones de estado R6, R7, R8, R11
Aplicación Reglas complejas, cambiantes o que dependen de contexto externo R9, R10

⚠️ Importante: Una restricción implementada solo en la aplicación puede saltarse con cualquier herramienta que acceda directamente a la base de datos (un script SQL, otra aplicación). Siempre que sea posible, implementa las restricciones en la base de datos, y deja en la aplicación solo las que no se puedan garantizar allí. En cualquier caso, el catálogo del modelo conceptual debe recogerlas todas, se implementen donde se implementen.


11. Ejercicios

Ejercicio 1: Identificar el tipo de restricción entre relaciones

Para cada enunciado, indica si se trata de exclusividad, exclusión, inclusividad o inclusión, y dibuja el fragmento de diagrama con la notación del curso:

  1. Un piloto de una aerolínea solo puede ser comandante de un modelo de avión en el que haya sido antes copiloto.
  2. Un artículo científico se publica en una revista o en las actas de un congreso, pero no en ambas.
  3. Un revisor de un congreso no puede revisar un artículo del que es autor.
  4. Para ser tutor de alumnos en prácticas, un empleado tiene que haber completado al menos 3 cursos de formación.
Ver solución

1. Inclusión. El par (piloto, modelo) de comandante debe estar en copiloto: no basta con haber sido copiloto de cualquier modelo.

flowchart LR
    PILOTO[PILOTO] ---|"(0,n)"| copiloto{"copiloto<br/>N:M"}
    copiloto ---|"(0,n)"| MODELO[MODELO_AVION]
    PILOTO ---|"(0,n)"| comandante{"comandante<br/>N:M"}
    comandante ---|"(0,n)"| MODELO
    comandante -.->|"inclusión"| copiloto

2. Exclusividad (total, si todo artículo se publica en algún sitio; parcial si puede haber artículos no publicados). Afecta a cada ocurrencia de ARTICULO, que participa en una relación u otra.

flowchart LR
    ARTICULO[ARTICULO] ---|"(0,n)"| en_rev{"publicado_en<br/>1:N"}
    en_rev ---|"(0,1)"| REVISTA[REVISTA]
    ARTICULO ---|"(0,n)"| en_con{"presentado_en<br/>1:N"}
    en_con ---|"(0,1)"| CONGRESO[CONGRESO]
    en_rev -.-|"exclusividad"| en_con

3. Exclusión. Un investigador puede ser autor de unos artículos y revisor de otros; lo prohibido es el mismo par (investigador, artículo) en las dos relaciones.

flowchart LR
    INVESTIGADOR[INVESTIGADOR] ---|"(1,n)"| escribe{"escribe<br/>N:M"}
    escribe ---|"(0,n)"| ARTICULO[ARTICULO]
    INVESTIGADOR ---|"(0,n)"| revisa{"revisa<br/>N:M"}
    revisa ---|"(0,n)"| ARTICULO
    escribe -.-|"exclusión"| revisa

4. Inclusividad (3,n). Importa cuántos cursos ha completado el empleado, no cuáles.

flowchart LR
    EMPLEADO[EMPLEADO] ---|"(0,1)"| tutoriza{"tutoriza<br/>1:N"}
    tutoriza ---|"(0,n)"| ALUMNO[ALUMNO_PRACTICAS]
    EMPLEADO ---|"(0,n)"| completa{"completa<br/>N:M"}
    completa ---|"(0,n)"| CURSO[CURSO]
    tutoriza -.->|"inclusividad (3,n)"| completa

Cada alumno en prácticas tiene 0 o 1 tutor (0,1) junto a EMPLEADO; un empleado tutoriza de 0 a n alumnos.

Ejercicio 2: Detectar y resolver trampas

Un hospital ha modelado lo siguiente: cada PLANTA tiene varias HABITACIONes (1:N); cada PLANTA tiene asignados varios ENFERMEROs (1:N); cada PACIENTE ingresado puede estar asignado a un ENFERMERO responsable (0..1) y cada enfermero atiende a varios pacientes. Se quieren responder estas preguntas: (a) ¿en qué habitación trabaja el enfermero X?; (b) ¿en qué planta está ingresado el paciente Y? Analiza qué trampas hay y propón un diagrama corregido. Supón que cada enfermero trabaja en una planta (no en una habitación concreta) y que cada paciente ocupa una habitación.

Ver solución

Pregunta (a): no es una trampa de abanico, sino una pregunta mal planteada: el enfermero trabaja en una planta, no en una habitación. El abanico PLANTA → HABITACION / PLANTA → ENFERMERO es correcto porque el negocio no relaciona enfermeros con habitaciones. Es importante distinguir “el modelo no responde porque falta información” de “el modelo no responde porque la pregunta no tiene sentido en la realidad”.

Pregunta (b): trampa de sima. El único camino del paciente a la planta es PACIENTE → ENFERMERO → PLANTA, y la participación del paciente en la relación con el enfermero es opcional. Para los pacientes sin enfermero asignado no podemos saber la planta. Además falta la información esencial: la habitación del paciente.

Diagrama corregido:

flowchart LR
    PLANTA[PLANTA] ---|"(1,1)"| tiene{"tiene<br/>1:N"}
    tiene ---|"(1,n)"| HABITACION[HABITACION]
    PLANTA ---|"(1,1)"| asignado{"asignado<br/>1:N"}
    asignado ---|"(1,n)"| ENFERMERO[ENFERMERO]
    HABITACION ---|"(1,1)"| ocupa{"ocupa<br/>1:N"}
    ocupa ---|"(0,n)"| PACIENTE[PACIENTE]
    ENFERMERO ---|"(0,1)"| responsable{"responsable<br/>1:N"}
    responsable ---|"(0,n)"| PACIENTE

Ahora la planta del paciente se obtiene por PACIENTE → HABITACION → PLANTA, que no tiene mínimos 0 en ese sentido. Aparece un ciclo (PLANTA–HABITACION–PACIENTE–ENFERMERO–PLANTA) que hay que analizar: si el negocio exige que el enfermero responsable trabaje en la planta del paciente, se anota R1: el enfermero responsable de un paciente está asignado a la planta de la habitación que ocupa el paciente.

(Si interesa la historia de ingresos, ocupa debería convertirse en una entidad INGRESO con fechas, por las razones de la sección 8.)

Ejercicio 3: Analizar ciclos

Para cada ciclo, indica si alguna relación es redundante, si hay una restricción de caminos coincidentes o si el ciclo es independiente:

  1. CIUDAD –está_en (N:1)– PROVINCIA –está_en (N:1)– COMUNIDAD; y CIUDAD –pertenece (N:1)– COMUNIDAD.
  2. EMPLEADO –trabaja (N:1)– DEPARTAMENTO; EMPLEADO –participa (N:M)– PROYECTO; PROYECTO –gestionado_por (N:1)– DEPARTAMENTO.
  3. JUGADOR –juega_en (N:1)– EQUIPO; EQUIPO –compite_en (N:1)– LIGA; JUGADOR –inscrito_en (N:1)– LIGA.
Ver solución
  1. Redundante. Cada ciudad está en una provincia, y cada provincia en una comunidad: la comunidad de la ciudad se deduce siempre. CIUDAD pertenece COMUNIDAD debe eliminarse.

  2. Independiente (salvo regla del negocio). Un empleado puede participar en proyectos gestionados por otros departamentos. Ninguna relación se deduce de las otras. Si el negocio dijera “solo se participa en proyectos del propio departamento”, sería una restricción de caminos coincidentes; si dijera “al menos el director del proyecto es del departamento gestor”, sería otra restricción distinta. Así es en el esquema EMPRESA: ASIGNACIONES permite asignar empleados de cualquier departamento a cualquier proyecto.

  3. Depende del tiempo y del negocio. Si un jugador solo está en un equipo y el equipo en una liga, inscrito_en parece redundante. Pero en muchas competiciones un jugador puede estar inscrito en una liga sin equipo (agente libre), o los traspasos de mitad de temporada hacen que la inscripción y el equipo actual no coincidan. Si inscrito_en siempre coincide con juega_en + compite_en, es redundante; si no, el ciclo se mantiene con una restricción (“si un jugador juega en un equipo, está inscrito en la liga de ese equipo”).

Ejercicio 4: Historia de precios

Una cadena de gasolineras quiere guardar el precio de cada combustible (gasóleo A, sin plomo 95…) en cada estación de servicio. Los precios cambian varias veces al día, y se necesita saber qué precio tenía cada combustible en cada estación en cualquier momento del pasado, además de las ventas realizadas (fecha y hora, litros, combustible, estación, importe cobrado). Dibuja el diagrama y escribe las restricciones necesarias.

Ver solución

El precio depende del combustible y de la estación y del momento: se modela como entidad.

flowchart TB
    ESTACION[ESTACION] --- e_id([<u>cod_estacion</u>])
    ESTACION --- e_dir([direccion])
    COMBUSTIBLE[COMBUSTIBLE] --- c_id([<u>cod_combustible</u>])
    COMBUSTIBLE --- c_nom([nombre])
    PRECIO[PRECIO] --- pr_id([<u>id_precio</u>])
    PRECIO --- pr_ini([inicio_vigencia])
    PRECIO --- pr_fin(["fin_vigencia (0,1)"])
    PRECIO --- pr_imp([precio_litro])
    ESTACION ---|"(1,1)"| fija{"fija<br/>1:N"}
    fija ---|"(0,n)"| PRECIO
    PRECIO ---|"(0,n)"| para{"para<br/>1:N"}
    para ---|"(1,1)"| COMBUSTIBLE
    VENTA[VENTA] --- v_id([<u>num_venta</u>])
    VENTA --- v_fh([fecha_hora])
    VENTA --- v_lit([litros])
    VENTA --- v_imp([importe])
    ESTACION ---|"(1,1)"| en{"en<br/>1:N"}
    en ---|"(0,n)"| VENTA
    VENTA ---|"(0,n)"| de{"de<br/>1:N"}
    de ---|"(1,1)"| COMBUSTIBLE

Restricciones:

  • R1: fin_vigencia > inicio_vigencia.
  • R2: para una misma estación y combustible, los periodos de vigencia de los precios no se solapan.
  • R3: para una misma estación y combustible hay como mucho un precio vigente (sin fin_vigencia).
  • R4: litros > 0 e importe > 0.
  • R5: el importe de una venta es litros × precio_litro del precio vigente en fecha_hora para esa estación y combustible (se guarda el importe cobrado, que es un dato congelado y puede incluir redondeos o descuentos; la regla sirve para validar).

Decisiones discutibles:

  • PRECIO podría ser entidad débil de ESTACION y COMBUSTIBLE con inicio_vigencia como identificador parcial. Es igual de correcto; se ha preferido un identificador propio por simplicidad.
  • No se relaciona VENTA con PRECIO: el precio aplicado se deduce por fechas, y además el importe cobrado se conserva.

Ejercicio 5: Relación con repetición

Un gimnasio registra las sesiones a las que asiste cada socio: un socio puede asistir muchas veces a la misma actividad (spinning, yoga…), incluso dos veces el mismo día (mañana y tarde). Cada asistencia tiene una hora de entrada y una valoración opcional (1 a 5). Además, cada actividad la imparte en cada sesión un monitor. Dibuja el diagrama.

Ver solución

La clave está en reconocer que hay dos acontecimientos: la sesión (una actividad impartida un día a una hora por un monitor) y la asistencia de un socio a una sesión.

flowchart TB
    classDef debil stroke-width:4px
    ACTIVIDAD[ACTIVIDAD] --- a_id([<u>cod_actividad</u>])
    ACTIVIDAD --- a_nom([nombre])
    ACTIVIDAD ---|"(1,1)"| programa{"programa<br/>ID"}:::debil
    programa ---|"(0,n)"| SESION[SESION]:::debil
    SESION --- s_fh(["<u>fecha_hora_inicio</u> parcial"])
    SESION --- s_sal([sala])
    MONITOR[MONITOR] ---|"(1,1)"| imparte{"imparte<br/>1:N"}
    imparte ---|"(0,n)"| SESION
    SOCIO[SOCIO] ---|"(0,n)"| asiste{"asiste<br/>N:M"}
    asiste ---|"(0,n)"| SESION
    asiste --- as_ent([hora_entrada])
    asiste --- as_val(["valoracion (0,1)"])
    SOCIO --- so_id([<u>num_socio</u>])

Con SESION como entidad, la relación asiste entre SOCIO y SESION ya no necesita repetición: un socio asiste como mucho una vez a cada sesión concreta; las repeticiones de la actividad son sesiones distintas. Este es un patrón muy útil: a veces la repetición desaparece si identificas el acontecimiento que faltaba.

Restricciones:

  • R1: valoracion entre 1 y 5.
  • R2: un monitor no puede impartir dos sesiones que se solapen en el tiempo (necesita la duración de la actividad).
  • R3: el número de asistentes a una sesión no supera el aforo de la sala.

Ejercicio 6: Catálogo de restricciones

Redacta un catálogo de al menos 8 restricciones para el siguiente fragmento, indicando el tipo y la implementación prevista: un hotel tiene HABITACION (número, tipo, precio_noche, capacidad), CLIENTE (dni, nombre, fecha_nacimiento, email) y RESERVA (número, fecha_entrada, fecha_salida, num_huespedes, importe, estado), con cada reserva de un cliente para una habitación.

Ver solución
Id Restricción Tipo Implementación
R1 El tipo de una habitación es ‘INDIVIDUAL’, ‘DOBLE’ o ‘SUITE’ Dominio CHECK ... IN
R2 precio_noche > 0 Dominio CHECK
R3 capacidad entre 1 y 6 Dominio CHECK
R4 El email del cliente es único Unicidad UNIQUE
R5 fecha_salida > fecha_entrada Entre atributos CHECK de tabla
R6 num_huespedes ≥ 1 y no supera la capacidad de la habitación reservada Entre entidades Trigger o aplicación (la primera parte, CHECK)
R7 Dos reservas no canceladas de la misma habitación no pueden solaparse en fechas Entre ocurrencias / temporal Trigger o aplicación
R8 El cliente titular de una reserva es mayor de edad en la fecha de entrada Entre entidades / temporal Trigger o aplicación
R9 El estado de una reserva es ‘CONFIRMADA’, ‘CANCELADA’, ‘EN_CURSO’ o ‘FINALIZADA’ Dominio CHECK ... IN
R10 Una reserva ‘FINALIZADA’ o ‘CANCELADA’ no puede volver a ‘CONFIRMADA’ Transición Trigger o aplicación
R11 El importe se calcula como noches × precio_noche vigente al confirmar, y no cambia aunque cambie el precio de la habitación Derivación / temporal Aplicación; el importe se almacena (dato congelado)

Fíjate en R8: necesita la fecha de nacimiento del cliente y la fecha de entrada de la reserva, es decir, dos entidades. No puede ser un CHECK. Y en R11: el importe no es un atributo derivado en sentido estricto, porque no debe recalcularse si cambia el precio.

Ejercicio 7 (propuesto): Biblioteca con restricciones

Diseña el modelo de préstamos de una biblioteca (libros, ejemplares, socios, préstamos y reservas) con las siguientes reglas, y decide para cada una si se recoge en el diagrama (y cómo) o en el catálogo: un socio puede reservar un libro (no un ejemplar) solo si no hay ningún ejemplar disponible; un socio no puede reservar un libro que ya tiene prestado; un socio sancionado no puede pedir préstamos; un ejemplar de consulta en sala no se presta nunca; la duración máxima de un préstamo es de 15 días para alumnos y 30 para profesores.

Ejercicio 8 (propuesto): Trayectoria de un futbolista

Modela la trayectoria completa de los jugadores de una federación: equipos en los que han jugado (con posibilidad de volver a un equipo anterior), cesiones (un jugador pertenece a un equipo pero juega cedido en otro durante una temporada), dorsal en cada equipo y temporada, y goles marcados en cada partido. Identifica todas las relaciones con repetición, las restricciones temporales y los posibles ciclos, y justifica cómo los resuelves.


Resumen

Concepto Idea clave Representación / tratamiento
Restricción explícita Regla que el diagrama no puede expresar Nota R1... en el diagrama y catálogo del diccionario
Exclusividad Cada ocurrencia participa en una u otra relación r1 -.-\|"exclusividad"\| r2
Exclusión El mismo par no puede estar en las dos relaciones r1 -.-\|"exclusión"\| r2
Inclusividad Para participar en R2 hay que haber participado (mín, máx) veces en R1 r2 -.->\|"inclusividad (mín,máx)"\| r1
Inclusión R2 ⊆ R1: el mismo par debe estar en R1 r2 -.->\|"inclusión"\| r1
Reflexivas Irreflexividad, aciclicidad, simetría, profundidad Notas en el diagrama
Trampa de abanico Dos 1:N desde la misma entidad: camino ambiguo Reestructurar las relaciones
Trampa de sima Participación opcional en el camino: camino interrumpido Añadir la relación directa
Ciclo Camino cerrado de relaciones Analizar: redundante, caminos coincidentes o independiente
Redundancia controlada Dato deducible que se mantiene a propósito Justificar, documentar y garantizar su consistencia
Atributo histórico Interesan los valores pasados Entidad de historial débil con periodo de vigencia
Relación histórica 1:N actual se convierte en N:M en el tiempo Entidad con fechas (estancia, periodo)
Dato congelado Valor fijado en el momento de la transacción Atributo propio, no derivado (precio_unitario)
N:M con repetición El mismo par se repite en el tiempo Reificar la relación en una entidad
Atributo derivado Se calcula a partir de otros datos Borde discontinuo + regla de derivación
Catálogo de restricciones Lista atómica y verificable de reglas Id, enunciado, elementos, tipo, implementación