Bases de datos
Banco de ejercicios
Ejercicios de modelado entidad-relación ordenados por dificultad, desde poner símbolos hasta resolver un enunciado completo, todos con solución explicada.
Cómo usar esta página
Los ejercicios están ordenados de menos a más y agrupados por lo que ejercitan. No hace falta hacerlos en orden ni todos de una vez: si un tema concreto no te queda claro, ve directo a su nivel.
Todos tienen la solución explicada, no solo la respuesta. Intenta resolverlo en papel antes de abrirla — el modelado se aprende equivocándose y viendo por qué, no leyendo modelos correctos.
| Nivel | Qué ejercita | Página de referencia |
|---|---|---|
| 1 | Los símbolos #, *, o | Los símbolos |
| 2 | Entidad contra atributo | Entidades y atributos |
| 3 | Cardinalidad y opcionalidad | Relaciones |
| 4 | Muchos a muchos | Relaciones N:M |
| 5 | Enunciados completos | Todo lo anterior |
Para los de nivel 5, date un límite de tiempo: 15 minutos por enunciado. Modelar es una habilidad que se practica bajo presión (en un parcial, en una reunión con un cliente), y tener el reloj corriendo obliga a decidir en vez de dar vueltas.
Nivel 1 · Los símbolos
Cada auto se identifica por su patente. La marca y el modelo se cargan siempre. El color y el kilometraje a veces faltan cuando el auto todavía no llegó al local.
AUTO
patente
marca
modelo
color
kilometrajeEl legajo identifica al empleado. Nombre y fecha de ingreso se cargan al contratarlo. La fecha de egreso solo se conoce cuando la persona se va, y el email corporativo se asigna días después del ingreso.
EMPLEADO
legajo
nombre
fecha_ingreso
fecha_egreso
email_corporativoEn una entidad PRODUCTO, ¿sirve descripcion como identificador único?
Este modelo tiene cinco errores. Encuéntralos.
┌────────────────────────────────┐
│ RESERVAS │
├────────────────────────────────┤
│ o id_reserva │
│ * fecha_reserva │
│ * fecha_cancelacion │
│ # email_cliente │
│ * cantidad_noches │
│ * precio_total │
└────────────────────────────────┘(precio_total se calcula multiplicando las noches por la tarifa de la habitación.)
1. RESERVAS está en plural. Debe ser RESERVA: la entidad nombra qué es cada instancia.
2. o id_reserva. Es el identificador, así que debe ser #. Un UID nunca puede ser opcional.
3. * fecha_cancelacion. La gran mayoría de las reservas nunca se cancelan. Marcarla obligatoria haría imposible guardar una reserva vigente. Debe ser o.
4. # email_cliente. Dos problemas: como UID falla (el mismo cliente hace muchas reservas, así que ni siquiera es único), y el cliente no va como atributo suelto sino como entidad relacionada.
5. * precio_total es un atributo derivado. Se calcula con cantidad_noches y la tarifa, así que guardarlo duplica información que puede quedar inconsistente.
Aunque acá hay matiz: el precio de una reserva es un valor histórico, igual que el precio_unitario de una factura. Si la tarifa de la habitación sube después, la reserva tiene que seguir diciendo lo que se cobró. En ese caso guardarlo es correcto — pero entonces hay que guardar el precio acordado, no recalcularlo. La decisión depende del negocio, y lo que se evalúa es que sepas justificarla.
┌────────────────────────────────┐
│ RESERVA │
├────────────────────────────────┤
│ # id_reserva │
│ * fecha_reserva │
│ * cantidad_noches │
│ * precio_acordado │
│ o fecha_cancelacion │
└────────────────────────────────┘
│
└── relación hacia CLIENTE y hacia HABITACIONNivel 2 · Entidad o atributo
“De cada producto guardamos nombre, precio y stock. Cada producto es de una marca, y de cada marca nos interesa su país de origen y su sitio web.”
“De cada prenda guardamos tipo, talle, color y precio.” No se guarda ningún otro dato sobre los colores.
Este modelo puso todo en una sola entidad. Sepáralo correctamente.
┌────────────────────────────────────┐
│ CONSULTA_MEDICA │
├────────────────────────────────────┤
│ # id_consulta │
│ * fecha │
│ * nombre_paciente │
│ * telefono_paciente │
│ * obra_social_paciente │
│ * nombre_medico │
│ * especialidad_medico │
│ * matricula_medico │
│ * diagnostico │
└────────────────────────────────────┘Los sufijos _paciente y _medico son la pista: cada vez que varios atributos comparten un sufijo, ahí hay una entidad escondida.
┌──────────────────────┐ ┌──────────────────┐ ┌──────────────────────┐
│ PACIENTE │ │ CONSULTA │ │ MEDICO │
├──────────────────────┤ ├──────────────────┤ ├──────────────────────┤
│ # id_paciente │──<│ * fecha │>──│ # matricula │
│ * nombre │ │ * diagnostico │ │ * nombre │
│ o telefono │ └──────────────────┘ │ * especialidad │
│ o obra_social │ └──────────────────────┘
└──────────────────────┘Lo que se gana:
- El teléfono de un paciente se escribe una vez, no una por consulta. Si cambia, se corrige en un solo lugar.
- Un paciente puede existir aunque nunca se haya atendido (se registró y todavía no vino).
- Un médico también, y su especialidad no se repite en cada consulta que atendió.
- Borrar una consulta no borra al paciente ni al médico.
Detalles de las decisiones:
matriculaes el UID natural de MEDICO — es única por ley, obligatoria y no cambia. Es uno de los pocos casos donde un UID natural es claramente la mejor opción.telefonoyobra_socialpasaron ao. Estaban como*porque en la tabla plana toda consulta tenía que llenar todo; ya separados, un paciente puede no tener obra social o no haber dejado teléfono.- CONSULTA es la entidad intermedia de la N:M entre pacientes y médicos, con sus atributos propios
fechaydiagnostico. Su UID no es barrado: el mismo paciente puede volver al mismo médico muchas veces, así que corresponde unid_consultapropio, o incluir la fecha en el UID.
Nivel 3 · Cardinalidad y opcionalidad
Un país tiene muchas ciudades y cada ciudad está en un solo país. Un país recién creado en el sistema podría no tener ciudades cargadas todavía.
Cada CIUDAD pertenecer a PAIS.
Cada PAIS contener CIUDADES.“Un cliente puede hacer varios pedidos. Todo pedido corresponde a un cliente. Un cliente que se registra hoy todavía no hizo ninguno.”
Léelas en voz alta y di qué está mal en cada una.
a) EMPLEADO ──────────────────< DEPARTAMENTO
b) AUTOR >- - - - - - - - - - - LIBRO
c) FACTURA ──────────────────── LINEA_FACTURA(Reglas del negocio: un empleado trabaja en un departamento y un departamento tiene muchos empleados; un libro tiene un autor y un autor escribe muchos libros; una factura tiene varias líneas.)
a) EMPLEADO ─────< DEPARTAMENTO
Se lee: “Cada empleado debe trabajar en uno o más departamentos.”
La pata de gallo está del lado equivocado. Los “muchos” son los empleados, no los departamentos. Correcto:
DEPARTAMENTO - - - - - - - -< EMPLEADOb) AUTOR >- - - - - LIBRO
Se lee: “Cada libro puede ser escrito por uno o más autores” y “cada autor puede escribir uno y solo un libro.”
Los dos extremos están invertidos: la pata de gallo debería tocar a LIBRO, y el trazo del lado de AUTOR debería ser continuo (todo libro tiene autor). Correcto:
AUTOR - - - - - - - -< LIBROc) FACTURA ──────── LINEA_FACTURA
Se lee: “Cada factura debe tener una y sola una línea.”
Falta la pata de gallo: una factura tiene varias líneas. Y además el lado de LINEA_FACTURA debería ser obligatorio pero el de FACTURA continuo también tiene sentido acá (una factura sin ninguna línea no significa nada). Correcto:
FACTURA ───────────────< LINEA_FACTURAEl patrón de los tres: el error es siempre el mismo, poner la pata de gallo en la entidad que la frase nombra primero en vez de en la que tiene muchas instancias. Leer en voz alta lo detecta cada vez.
Una empresa modeló EMPLEADO y FICHA_MEDICA como dos entidades en relación 1:1. La ficha guarda grupo sanguíneo, alergias y contacto de emergencia; solo la tiene cargada el personal que pasó por el examen preocupacional, y solo el área de salud puede consultarla.
¿Está bien esa decisión?
Nivel 4 · Muchos a muchos
Los actores trabajan en películas; de cada participación se guarda el personaje que interpreta y su orden en los créditos. Completa la resolución de la N:M.
ACTOR ────< >──── PELICULA
REPARTO
personaje
orden_creditosUn blog: “Un artículo puede tener varias etiquetas y una etiqueta puede estar en varios artículos.” No se guarda ningún dato adicional sobre esa asociación.
Resuelve este modelo por completo:
Un supermercado tiene proveedores (nombre, CUIT, teléfono) y productos (código, nombre, stock). Un proveedor abastece varios productos y un producto puede venir de varios proveedores. De cada combinación proveedor–producto interesa el precio que cobra ese proveedor y en cuántos días entrega.
┌──────────────────┐ ┌────────────────────┐ ┌──────────────────┐
│ PROVEEDOR │ │ ABASTECIMIENTO │ │ PRODUCTO │
├──────────────────┤ ├────────────────────┤ ├──────────────────┤
│ # cuit │──┼<│ * precio │>┼─│ # codigo │
│ * nombre │ │ * dias_entrega │ │ * nombre │
│ o telefono │ └────────────────────┘ │ * stock │
└──────────────────┘ └──────────────────┘Lectura en voz alta:
Cada ABASTECIMIENTO debe corresponder a un y solo un PROVEEDOR. Cada ABASTECIMIENTO debe corresponder a un y solo un PRODUCTO. Cada PROVEEDOR puede abastecer uno o más PRODUCTOS. Cada PRODUCTO puede ser abastecido por uno o más PROVEEDORES.
Las decisiones:
cuites un buen UID natural de PROVEEDOR: único por ley, obligatorio para facturar, y no cambia. Es de los pocos casos donde no hace falta un id artificial.precioydias_entregavan en la entidad intermedia, y esa es la prueba de que hacía falta: no son del proveedor (cobra distinto por cada producto) ni del producto (cada proveedor lo cobra distinto). Son de la combinación.- El UID de ABASTECIMIENTO es barrado: el par (proveedor, producto) identifica un acuerdo, y no tiene sentido que el mismo proveedor tenga dos precios distintos para el mismo producto al mismo tiempo.
stockestá en PRODUCTO, no en ABASTECIMIENTO. Es el stock total del supermercado, no depende de quién lo trajo. Ponerlo en la intermedia sería un error clásico: obligaría a sumar filas para saber cuánto hay.
Extensión, si quieres seguir: ¿dónde pondrías el historial de precios, para saber qué cobraba ese proveedor el año pasado? La respuesta es una entidad más colgando de ABASTECIMIENTO, con la fecha desde la que rige cada precio — el mismo patrón que aparece cada vez que un dato necesita conservar su historia.
Ordena los pasos para resolver una relación muchos a muchos.
- Poner en ella los atributos propios del vínculo
- Buscarle el nombre que usa el negocio para ese hecho
- Reemplazar la relación original por dos relaciones 1:N hacia la nueva entidad
- Crear una entidad nueva entre las dos
- Decidir si su UID es barrado o si necesita un identificador propio
- Buscar si hay datos que pertenecen al vínculo y no a ninguna de las dos entidades
- Detectar la pata de gallo en los dos extremos de la relación
- Girar las dos patas de gallo hacia la entidad del medio
Nivel 5 · Enunciados completos
Un hotel tiene habitaciones, cada una con su número, tipo (simple, doble, suite) y tarifa por noche. Los huéspedes se registran con nombre, documento, email y teléfono opcional. Un huésped hace reservas: cada reserva es de una habitación, con fecha de entrada y de salida, y puede estar confirmada o cancelada. Un huésped puede tener varias reservas a lo largo del tiempo, y una habitación se reserva muchas veces.
Modela con símbolos, relaciones y opcionalidad. Justifica el UID de cada entidad.
┌──────────────────────┐ ┌──────────────────────┐ ┌──────────────────────┐
│ HUESPED │ │ RESERVA │ │ HABITACION │
├──────────────────────┤ ├──────────────────────┤ ├──────────────────────┤
│ # id_huesped │──<│ # id_reserva │>──│ # numero │
│ * documento │ │ * fecha_entrada │ │ * tipo │
│ * nombre │ │ * fecha_salida │ │ * tarifa_noche │
│ * email │ │ * estado │ └──────────────────────┘
│ o telefono │ └──────────────────────┘
└──────────────────────┘Lectura:
Cada RESERVA debe corresponder a un y solo un HUÉSPED. Cada RESERVA debe ser de una y sola una HABITACIÓN. Cada HUÉSPED puede tener una o más RESERVAS. Cada HABITACIÓN puede tener una o más RESERVAS.
Los UID:
- HABITACION:
numero, natural. El hotel ya numera sus habitaciones y ese número no cambia. - HUESPED:
id_huespedartificial, condocumentomarcado como único aparte. El documento parece un buen UID natural, pero falla con extranjeros de documentación distinta y con cargas erróneas que hay que corregir. - RESERVA:
id_reservapropio, NO barrado. Este es el punto interesante: un UID barrado con (huésped, habitación) prohibiría que el mismo huésped vuelva a la misma habitación en otro viaje. Agregarfecha_entradalo arreglaría, pero un id propio es más simple y es lo que el hotel usa para hablar con el cliente (“su reserva número 4471”).
Otras decisiones:
estadoes*con valores confirmada/cancelada. Toda reserva tiene un estado desde que se crea.tarifa_nocheestá en HABITACION, pero ojo: si las tarifas cambian por temporada, el precio de una reserva vieja se perdería. Un modelo más completo guardaría elprecio_acordadoen RESERVA, por el mismo motivo delprecio_unitariode una factura.- Lo que el modelo no impide: dos reservas confirmadas de la misma habitación en fechas superpuestas. Ninguna restricción del modelo E-R puede expresar eso; se controla con lógica de aplicación o con restricciones específicas del motor. Reconocer ese límite es parte de saber modelar.
Una plataforma ofrece cursos, cada uno con título, descripción y precio. Cada curso lo creó un instructor (nombre, email, biografía). Un curso se divide en lecciones ordenadas, cada una con título y duración en minutos. Los usuarios (nombre, email, fecha de registro) se inscriben en cursos pagando un precio, y la plataforma registra cuándo se inscribieron y qué porcentaje del curso completaron. Además, un usuario puede dejar una reseña de un curso en el que está inscripto, con puntaje y comentario.
┌────────────────┐
│ INSTRUCTOR │
├────────────────┤
│ # id_instructor│
│ * nombre │
│ * email │
│ o biografia │
└────────────────┘
│ crea
│
┌────────────────┐ ┌──────────────────┐
│ CURSO │────────<│ LECCION │
├────────────────┤ ├──────────────────┤
│ # id_curso │ │ # id_leccion │
│ * titulo │ │ * titulo │
│ * precio │ │ * duracion_min │
│ o descripcion │ │ * orden │
└────────────────┘ └──────────────────┘
│ │
│ └──────────────────┐
│ │
┌──────────────────┐ ┌──────────────────┐
│ INSCRIPCION │ │ RESEÑA │
├──────────────────┤ ├──────────────────┤
│ # id_inscripcion │ │ # id_reseña │
│ * fecha │ │ * puntaje │
│ * precio_pagado │ │ * fecha │
│ * porcentaje │ │ o comentario │
└──────────────────┘ └──────────────────┘
│ │
┌────────────────┐ │
│ USUARIO │──────────────┘
├────────────────┤
│ # id_usuario │
│ * nombre │
│ * email │
│ * fecha_registro│
└────────────────┘Las cinco decisiones que definen este modelo:
1. LECCION es 1:N desde CURSO, no N:M. Una lección pertenece a un solo curso. El atributo orden es lo que permite mostrarlas en secuencia; sin él, no habría forma de saber cuál va primero.
2. INSCRIPCION resuelve la N:M entre USUARIO y CURSO, con tres atributos propios: fecha, precio_pagado y porcentaje. El precio_pagado está separado del precio del curso a propósito: si el curso sube de precio o se vende con descuento, hay que conservar lo que efectivamente se cobró.
3. RESEÑA es una entidad aparte, no atributos de INSCRIPCION. Es la decisión más discutible del modelo y hay que poder defenderla: como no toda inscripción genera una reseña, meter puntaje y comentario en INSCRIPCION dejaría esas columnas vacías en la mayoría de las filas. Separarla también permitiría después permitir más de una, o moderarlas.
4. La regla “solo se puede reseñar un curso en el que estás inscripto” no la expresa el modelo. Se podría forzar haciendo que RESEÑA cuelgue de INSCRIPCION en vez de conectar con USUARIO y CURSO por separado. Es una alternativa mejor si esa regla es estricta — y notar la diferencia entre las dos opciones es exactamente lo que se evalúa en este ejercicio.
5. porcentaje es un atributo derivado guardado a propósito. Se podría calcular contando lecciones vistas, pero eso requeriría otra entidad (LECCION_VISTA) que el enunciado no pide. Guardarlo es una simplificación consciente, no un descuido — y si más adelante hiciera falta saber qué lecciones vio, esa entidad aparecería y el porcentaje pasaría a calcularse.
Una aerolínea opera vuelos identificados por un número (AR1234), con aeropuerto de origen, de destino y horarios de salida y llegada. Cada vuelo se opera con un avión (matrícula, modelo, cantidad de asientos). Los pasajeros (documento, nombre, email) compran pasajes para un vuelo en una fecha determinada: el mismo vuelo AR1234 sale todos los días, y cada salida es distinta. De cada pasaje se guarda el asiento y la clase (turista o ejecutiva). Los aeropuertos tienen código IATA, nombre y ciudad.
Este enunciado tiene una trampa. Encuéntrala.
La trampa está en “el mismo vuelo AR1234 sale todos los días, y cada salida es distinta”.
Hay que distinguir dos cosas que el lenguaje común llama igual:
- VUELO — la ruta programada: AR1234 va de EZE a MAD, sale 22:00, llega 15:30. Es una definición que se repite todos los días.
- VUELO_PROGRAMADO (o salida) — la instancia concreta: AR1234 del 15 de marzo de 2026, con ese avión y esos pasajeros.
Un pasaje no es para “AR1234”: es para AR1234 de un día determinado. Y el avión asignado puede cambiar de un día para otro. Modelar sin esa separación hace imposible saber quién viajó en qué salida.
┌──────────────────┐
│ AEROPUERTO │
├──────────────────┤
│ # codigo_iata │───┐ origen
│ * nombre │───┤ destino
│ * ciudad │ │
└──────────────────┘ │
│
┌──────────────────────┴───┐ ┌──────────────────┐
│ VUELO │ │ AVION │
├──────────────────────────┤ ├──────────────────┤
│ # numero │ │ # matricula │
│ * hora_salida │ │ * modelo │
│ * hora_llegada │ │ * cant_asientos │
└──────────────────────────┘ └──────────────────┘
│ │
└────────────┐ ┌────────────┘
┌──────────────────────┐
│ VUELO_PROGRAMADO │
├──────────────────────┤
│ # id_vuelo_prog │
│ * fecha │
│ * estado │
└──────────────────────┘
│
┌──────────────────────┐ ┌──────────────────┐
│ PASAJE │────│ PASAJERO │
├──────────────────────┤ ├──────────────────┤
│ # id_pasaje │ │ # id_pasajero │
│ * asiento │ │ * documento │
│ * clase │ │ * nombre │
│ * precio │ │ * email │
└──────────────────────┘ └──────────────────┘Lo demás que hay que notar:
- AEROPUERTO se relaciona dos veces con VUELO, una como origen y otra como destino. Dos relaciones distintas entre las mismas dos entidades es perfectamente válido, y es la única forma de expresarlo. En el modelo relacional se convierte en dos claves foráneas hacia la misma tabla.
- PASAJE es la entidad intermedia entre PASAJERO y VUELO_PROGRAMADO, con
asiento,claseypreciocomo atributos del vínculo. - El UID de VUELO_PROGRAMADO podría ser barrado con (número de vuelo, fecha), que es único y significativo. Un id propio también sirve y es más cómodo de referenciar desde PASAJE.
codigo_iataes un UID natural excelente: internacionalmente único, estable y ya usado en todos lados. Es el ejemplo de libro de cuándo no inventar un id artificial.
La lección general: cuando un enunciado dice “cada X se repite” o “el mismo X pero en otra fecha”, casi siempre hay dos entidades donde parecía haber una: la plantilla y la ocurrencia. Pasa con vuelos y salidas, con materias y cursadas, con clases de gimnasio y sus sesiones, con series y episodios.
Lista de verificación
Antes de dar un modelo por terminado, revisa esto. Es la misma lista con la que se corrige un parcial:
| ✓ | Verificar |
|---|---|
| ☐ | Toda entidad tiene un UID (#) |
| ☐ | Los nombres de entidad están en singular |
| ☐ | Cada atributo tiene su símbolo: #, * u o |
| ☐ | Ningún atributo es multivaluado (nada de tel1, tel2) |
| ☐ | Ningún atributo compuesto quedó sin descomponer |
| ☐ | Ningún atributo derivado se guarda sin motivo |
| ☐ | No queda ninguna relación N:M sin resolver |
| ☐ | Cada relación tiene un nombre que es un verbo concreto |
| ☐ | Cada relación se leyó en voz alta en las dos direcciones |
| ☐ | Cada extremo opcional se justificó con un caso real |
| ☐ | Ningún dato del negocio quedó sin lugar donde guardarse |
Si las once dan bien, el modelo está listo para pasar a tablas.
Y después
Esta sección cubre el modelado entidad-relación con Data Modeler. Los pasos naturales a partir de acá son la normalización (las formas normales, que llegan a conclusiones parecidas por un camino más formal) y el SQL para consultar las tablas que acabas de diseñar.