📐 ¿Qué es UML?
UML (Unified Modeling Language o Lenguaje Unificado de Modelado) es un lenguaje estándar de modelado gráfico que permite visualizar, especificar, construir y documentar los artefactos de un sistema software. No es un lenguaje de programación, sino un lenguaje de modelado: una notación visual estandarizada que sirve como plano o blueprint del software que se va a desarrollar.
Piensa en la relación entre un arquitecto y un edificio: antes de colocar un solo ladrillo, el arquitecto crea planos detallados que muestran la estructura, las conexiones y la distribución. UML cumple esa misma función en la ingeniería del software. Los ingenieros de software utilizan diagramas UML para representar la estructura de un sistema, el comportamiento de sus componentes y las interacciones entre ellos, antes de escribir una sola línea de código.
UML no está vinculado a ningún lenguaje de programación concreto ni a ninguna metodología de desarrollo. Puede utilizarse con Java, C++, Python, C# o cualquier otro lenguaje orientado a objetos. Igualmente, se aplica tanto en proyectos que siguen metodologías tradicionales (cascada, RUP) como en entornos ágiles (Scrum, XP), donde se utiliza de forma más ligera y selectiva.
📜 Historia y origen de UML
La historia de UML está ligada a la evolución del paradigma de la programación orientada a objetos. A finales de los años 80 y principios de los 90, existían decenas de métodos de modelado orientado a objetos diferentes e incompatibles entre sí. Cada experto tenía su propia notación: Grady Booch tenía el método Booch, James Rumbaugh desarrolló OMT (Object Modeling Technique) y Ivar Jacobson creó OOSE (Object-Oriented Software Engineering).
En 1994 se produjo un acontecimiento clave: Jim Rumbaugh abandonó General Electric para unirse a Grady Booch en Rational Software. Al año siguiente se les unió Ivar Jacobson. Estos tres investigadores, conocidos como los «Tres Amigos» (Three Amigos), comenzaron a unificar sus metodologías en un único lenguaje de modelado.
El resultado fue UML, que siguió esta línea temporal:
| Año | Hito |
|---|---|
| 1994-1995 | Booch y Rumbaugh comienzan la unificación en Rational Software |
| 1996 | Se forma el consorcio UML Partners (HP, IBM, Microsoft, Oracle, DEC...) |
| 1997 | UML 1.0 se presenta al OMG. Es adoptado como estándar |
| 2000 | UML 1.4 — Consolidación y amplia adopción industrial |
| 2005 | UML 2.0 — Revisión mayor con 14 tipos de diagramas |
| 2017 | UML 2.5.1 — Versión actual vigente (estándar ISO/IEC 19505) |
🎯 ¿Para qué sirve UML?
UML sirve para construir modelos de sistemas software. Un modelo es una representación simplificada de la realidad que nos ayuda a comprender un sistema complejo centrándose en los aspectos relevantes e ignorando los detalles innecesarios. Puesto que un único modelo no puede capturar toda la complejidad de un sistema, UML proporciona múltiples tipos de diagramas que ofrecen diferentes vistas complementarias del mismo sistema.
Las principales utilidades de UML son:
🔹 Comunicación entre equipos: UML proporciona un vocabulario visual común que permite a desarrolladores, arquitectos, analistas y stakeholders comunicarse de forma precisa sobre la estructura y el comportamiento de un sistema.
🔹 Documentación de arquitectura: Los diagramas UML sirven como documentación viva del diseño del sistema. Un nuevo miembro del equipo puede comprender rápidamente la arquitectura examinando los diagramas clave.
🔹 Detección temprana de problemas: Modelar antes de codificar permite identificar errores de diseño, dependencias circulares o inconsistencias antes de que se conviertan en código difícil de cambiar.
🔹 Generación de código: Algunas herramientas permiten generar esqueletos de código a partir de diagramas UML (ingeniería directa) o generar diagramas a partir de código existente (ingeniería inversa).
🔹 Planificación de pruebas: Los diagramas de casos de uso y de secuencia facilitan la identificación de escenarios de prueba y la planificación de la estrategia de testing.
📊 Tipos de diagramas UML
UML 2.5 define 14 tipos de diagramas agrupados en dos grandes categorías: diagramas estructurales (que muestran la parte estática del sistema) y diagramas de comportamiento (que representan la parte dinámica). A continuación se presenta la clasificación completa:
Diagramas estructurales (estáticos)
Muestran qué es el sistema: sus clases, objetos, componentes y cómo se organizan.
| Diagrama | Descripción | Uso principal |
|---|---|---|
| Clases | Muestra clases, atributos, métodos y relaciones | Diseño de la estructura del sistema |
| Objetos | Instantánea de objetos concretos y sus relaciones en un momento dado | Validar el diagrama de clases con ejemplos |
| Componentes | Organización y dependencias entre componentes del sistema | Arquitectura de módulos y librerías |
| Despliegue | Distribución física del software en nodos hardware | Infraestructura y DevOps |
| Paquetes | Agrupación lógica de elementos en paquetes | Organización de namespaces |
| Estructura compuesta | Estructura interna de una clase y sus colaboraciones | Componentes internos complejos |
| Perfil | Extensiones y personalizaciones de UML | Adaptar UML a dominios específicos |
Diagramas de comportamiento (dinámicos)
Muestran cómo actúa el sistema: las interacciones, los flujos y los cambios de estado a lo largo del tiempo.
| Diagrama | Descripción | Uso principal |
|---|---|---|
| Casos de uso | Funcionalidades del sistema desde la perspectiva del usuario | Captura de requisitos |
| Actividades | Flujo de trabajo o proceso paso a paso | Modelar procesos de negocio |
| Máquina de estados | Estados posibles de un objeto y sus transiciones | Ciclos de vida de entidades |
| Secuencia | Interacción entre objetos ordenada en el tiempo | Flujos de mensajes entre componentes |
| Comunicación | Interacción entre objetos con énfasis en la estructura | Colaboraciones entre objetos |
| Temporización | Cambios de estado con restricciones temporales | Sistemas en tiempo real |
| Vista de interacción | Combinación de diagramas de actividad y secuencia | Flujos complejos con variantes |
🏗️ Diagrama de clases
El diagrama de clases es el tipo de diagrama UML más importante y más utilizado. Muestra la estructura estática de un sistema representando las clases, sus atributos, sus métodos y las relaciones entre ellas. Es el puente natural entre el diseño y la implementación en lenguajes orientados a objetos como Java.
Representación de una clase
Cada clase se representa como un rectángulo dividido en tres compartimentos:
1. Nombre de la clase — en la parte superior, centrado y en negrita.
2. Atributos — en la parte central, con su visibilidad, nombre y tipo.
3. Métodos — en la parte inferior, con su visibilidad, nombre, parámetros y tipo de retorno.
La visibilidad se indica con estos símbolos:
| Símbolo UML | Significado | Equivalente Java |
|---|---|---|
+ | Público | public |
- | Privado | private |
# | Protegido | protected |
~ | Paquete | (acceso por defecto) |
Ejemplo: clase Producto en UML y Java
Veamos cómo se traduce una clase UML a código Java. La clase Producto tiene atributos privados, un constructor y métodos públicos:
Notación UML (textual):
┌──────────────────────────────┐
│ Producto │
├──────────────────────────────┤
│ - nombre: String │
│ - precio: double │
│ - stock: int │
├──────────────────────────────┤
│ + getNombre(): String │
│ + getPrecio(): double │
│ + getStock(): int │
│ + aplicarDescuento(p: double)│
│ + hayStock(): boolean │
└──────────────────────────────┘
Implementación en Java:
public class Producto {
private String nombre;
private double precio;
private int stock;
public Producto(String nombre, double precio, int stock) {
this.nombre = nombre;
this.precio = precio;
this.stock = stock;
}
public String getNombre() {
return nombre;
}
public double getPrecio() {
return precio;
}
public int getStock() {
return stock;
}
public void aplicarDescuento(double porcentaje) {
if (porcentaje > 0 && porcentaje < 100) {
this.precio -= this.precio * (porcentaje / 100);
}
}
public boolean hayStock() {
return stock > 0;
}
}
Salida esperada (al crear un producto y aplicar descuento):
Producto: Laptop Gaming
Precio original: 1200.0€
Precio con 15% dto: 1020.0€
¿Hay stock? true
👤 Diagrama de casos de uso
El diagrama de casos de uso modela la funcionalidad de un sistema desde la perspectiva del usuario. Es el primer diagrama que se suele crear en un proyecto porque captura los requisitos funcionales: qué puede hacer cada tipo de usuario con el sistema.
Los elementos fundamentales son:
🔹 Actor: una entidad externa que interactúa con el sistema (persona, otro sistema, dispositivo). Se representa con una figura de palo (stick figure).
🔹 Caso de uso: una funcionalidad que el sistema ofrece a un actor. Se representa con una elipse con el nombre dentro.
🔹 Límite del sistema: un rectángulo que enmarca los casos de uso, separando lo que está dentro del sistema de lo que está fuera.
🔹 Relaciones: asociación (línea continua), «include» (un caso de uso siempre invoca a otro), «extend» (un caso de uso opcionalmente extiende a otro) y generalización (herencia entre actores o casos de uso).
Ejemplo: sistema de tienda online
┌─────────────────── Sistema Tienda Online ───────────────────┐
│ │
│ ┌─────────────────────┐ │
│ │ Buscar productos │──── Actor: Cliente │
│ └─────────────────────┘ │
│ │
│ ┌─────────────────────┐ │
│ │ Añadir al carrito │──── Actor: Cliente │
│ └─────────────────────┘ │
│ │ │
│ «include» │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Realizar pedido │──── Actor: Cliente │
│ └─────────────────────┘ │
│ │ │
│ «include» │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Procesar pago │──── Actor: Pasarela de pago │
│ └─────────────────────┘ │
│ │ │
│ «extend» │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Aplicar cupón │ │
│ └─────────────────────┘ │
│ │
│ ┌─────────────────────┐ │
│ │ Gestionar stock │──── Actor: Administrador │
│ └─────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
En este ejemplo, «Realizar pedido» siempre incluye «Procesar pago» (relación «include»), mientras que «Aplicar cupón» es una extensión opcional del proceso de pago (relación «extend»). Observa que el sistema tiene dos actores con roles distintos: el Cliente y el Administrador.
⏱️ Diagrama de secuencia
El diagrama de secuencia es el diagrama de interacción más utilizado. Muestra cómo los objetos intercambian mensajes entre sí a lo largo del tiempo para llevar a cabo una funcionalidad concreta. El eje vertical representa el tiempo (de arriba a abajo) y el eje horizontal muestra los objetos participantes.
Elementos del diagrama de secuencia
🔹 Línea de vida: línea vertical punteada debajo de cada objeto, que indica su existencia durante la interacción.
🔹 Mensajes síncronos: flecha con punta sólida. El emisor espera la respuesta.
🔹 Mensajes asíncronos: flecha con punta abierta. El emisor no espera.
🔹 Retorno: flecha punteada que indica la respuesta a un mensaje.
🔹 Barra de activación: rectángulo sobre la línea de vida que indica que el objeto está procesando un mensaje.
Ejemplo: proceso de login
Usuario :LoginForm :AuthService :BaseDatos
│ │ │ │
│ ingresaDatos() │ │ │
│────────────────>│ │ │
│ │ validar(user, │ │
│ │ password) │ │
│ │────────────────>│ │
│ │ │ buscarUsuario() │
│ │ │────────────────>│
│ │ │ │
│ │ │ datosUsuario │
│ │ │<────────────────│
│ │ │ │
│ │ │ verificarHash()│
│ │ │────┐ │
│ │ │ │ (self) │
│ │ │<───┘ │
│ │ │ │
│ │ tokenSesión │ │
│ │<────────────────│ │
│ │ │ │
│ loginExitoso() │ │ │
│<────────────────│ │ │
│ │ │ │
Este diagrama muestra claramente la secuencia temporal de mensajes en un proceso de autenticación: el usuario envía sus datos al formulario, este los pasa al servicio de autenticación, que consulta la base de datos, verifica el hash de la contraseña y devuelve un token de sesión.
🔄 Diagrama de estados
El diagrama de máquina de estados (o simplemente diagrama de estados) muestra los diferentes estados por los que pasa un objeto a lo largo de su ciclo de vida y las transiciones que provocan los cambios de estado. Es especialmente útil para modelar entidades con ciclos de vida complejos.
Ejemplo: estados de un pedido
● (estado inicial)
│
▼
┌──────────┐ confirmar() ┌──────────┐ pagar() ┌──────────┐
│ PENDIENTE │────────────────>│ CONFIRMADO│────────────>│ PAGADO │
└──────────┘ └──────────┘ └──────────┘
│ │ │
│ cancelar() │ cancelar() │ enviar()
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ CANCELADO │ │ CANCELADO │ │ ENVIADO │
└──────────┘ └──────────┘ └──────────┘
│
│ entregar()
▼
┌──────────┐
│ ENTREGADO │
└──────────┘
│
▼
◉ (estado final)
Implementar este diagrama de estados en Java es directo usando un enum:
public enum EstadoPedido {
PENDIENTE, CONFIRMADO, PAGADO, ENVIADO, ENTREGADO, CANCELADO
}
public class Pedido {
private EstadoPedido estado;
public Pedido() {
this.estado = EstadoPedido.PENDIENTE;
}
public void confirmar() {
if (estado == EstadoPedido.PENDIENTE) {
estado = EstadoPedido.CONFIRMADO;
System.out.println("Pedido confirmado.");
} else {
System.out.println("Error: no se puede confirmar desde " + estado);
}
}
public void pagar() {
if (estado == EstadoPedido.CONFIRMADO) {
estado = EstadoPedido.PAGADO;
System.out.println("Pago procesado.");
} else {
System.out.println("Error: no se puede pagar desde " + estado);
}
}
public void enviar() {
if (estado == EstadoPedido.PAGADO) {
estado = EstadoPedido.ENVIADO;
System.out.println("Pedido enviado.");
} else {
System.out.println("Error: no se puede enviar desde " + estado);
}
}
public void cancelar() {
if (estado == EstadoPedido.PENDIENTE || estado == EstadoPedido.CONFIRMADO) {
estado = EstadoPedido.CANCELADO;
System.out.println("Pedido cancelado.");
} else {
System.out.println("Error: no se puede cancelar desde " + estado);
}
}
public EstadoPedido getEstado() {
return estado;
}
}
Salida esperada:
Pedido confirmado.
Pago procesado.
Pedido enviado.
Error: no se puede cancelar desde ENVIADO
🔗 Relaciones en UML
Las relaciones son los mecanismos que conectan los elementos de un diagrama UML. Entender las relaciones es fundamental para crear diagramas correctos y traducirlos fielmente a código. UML define cuatro tipos principales de relaciones:
Asociación
Una asociación es una relación estructural que indica que los objetos de una clase están conectados con los de otra. Se representa con una línea continua y puede tener multiplicidad en ambos extremos (por ejemplo, 1..* significa «uno o más»).
Profesor 1 ─────────── * Asignatura
"imparte"
Multiplicidades comunes: 1 (exactamente uno), 0..1 (cero o uno), * (cero o más), 1..* (uno o más), 3..5 (entre tres y cinco).
Agregación y composición
La agregación (diamante vacío ◇) indica una relación «tiene un» donde las partes pueden existir independientemente del todo. La composición (diamante relleno ◆) indica que las partes no pueden existir sin el todo.
Agregación: Universidad ◇───── Profesor
(si la universidad cierra, los profesores siguen existiendo)
Composición: Factura ◆───── LineaFactura
(si se elimina la factura, sus líneas no tienen sentido)
Generalización (herencia)
La generalización es la relación de herencia: un elemento hijo hereda los atributos y métodos del padre. Se representa con una flecha con punta triangular vacía apuntando hacia la clase padre.
UML: Vehiculo △──── Coche → class Coche extends Vehiculo
Vehiculo △──── Moto → class Moto extends Vehiculo
Realización (implementación de interface)
La realización conecta una interfaz con la clase que la implementa. Se representa con una flecha punteada con punta triangular vacía.
UML: «interface» Serializable ◁- - - - Producto
→ class Producto implements Serializable
Dependencia
Una dependencia indica que una clase utiliza temporalmente a otra (por ejemplo, como parámetro de un método). Se representa con una flecha punteada. Un cambio en la clase utilizada puede afectar a la que depende de ella.
☕ De UML a código Java: ejemplo integrador
Veamos un ejemplo completo que traduce un diagrama de clases a código Java funcional. Modelaremos un sistema de biblioteca con las relaciones más comunes: herencia, asociación y composición.
┌────────────────────┐ ┌────────────────────┐
│ «abstract» │ │ Prestamo │
│ Publicacion │ ├────────────────────┤
├────────────────────┤ │ - fechaPrestamo │
│ - titulo: String │ │ - fechaDevolucion │
│ - autor: String │ │ - publicacion │
│ - anio: int │ │ - socio │
├────────────────────┤ ├────────────────────┤
│ + getInfo(): String│ │ + estaVencido() │
│ + getTipo(): String│ │ + toString(): String│
└────────┬───────────┘ └────────────────────┘
│ ◆
┌────┴────┐ │
△ △ ┌──────┴─────────┐
┌────────┐ ┌────────┐ │ Biblioteca │
│ Libro │ │Revista │ ├────────────────┤
├────────┤ ├────────┤ │ - nombre │
│- paginas│ │- numero│ │ - prestamos │
├────────┤ ├────────┤ ├────────────────┤
│+getTipo│ │+getTipo│ │ + prestar() │
└────────┘ └────────┘ │ + devolver() │
│ + listarActivos│
└────────────────┘
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
// Clase abstracta: Publicacion
abstract class Publicacion {
private String titulo;
private String autor;
private int anio;
public Publicacion(String titulo, String autor, int anio) {
this.titulo = titulo;
this.autor = autor;
this.anio = anio;
}
public String getInfo() {
return titulo + " (" + autor + ", " + anio + ")";
}
public abstract String getTipo();
public String getTitulo() { return titulo; }
}
// Herencia: Libro extiende Publicacion
class Libro extends Publicacion {
private int paginas;
public Libro(String titulo, String autor, int anio, int paginas) {
super(titulo, autor, anio);
this.paginas = paginas;
}
@Override
public String getTipo() { return "Libro"; }
}
// Herencia: Revista extiende Publicacion
class Revista extends Publicacion {
private int numero;
public Revista(String titulo, String autor, int anio, int numero) {
super(titulo, autor, anio);
this.numero = numero;
}
@Override
public String getTipo() { return "Revista"; }
}
// Asociación: Prestamo vincula Publicacion con un socio
class Prestamo {
private LocalDate fechaPrestamo;
private LocalDate fechaDevolucion;
private Publicacion publicacion;
private String socio;
public Prestamo(Publicacion pub, String socio, int diasPrestamo) {
this.publicacion = pub;
this.socio = socio;
this.fechaPrestamo = LocalDate.now();
this.fechaDevolucion = fechaPrestamo.plusDays(diasPrestamo);
}
public boolean estaVencido() {
return LocalDate.now().isAfter(fechaDevolucion);
}
public Publicacion getPublicacion() { return publicacion; }
@Override
public String toString() {
return publicacion.getTipo() + ": " + publicacion.getInfo()
+ " → Socio: " + socio
+ " (devolver antes de " + fechaDevolucion + ")";
}
}
// Composición: Biblioteca contiene Prestamos
class Biblioteca {
private String nombre;
private List<Prestamo> prestamos;
public Biblioteca(String nombre) {
this.nombre = nombre;
this.prestamos = new ArrayList<>();
}
public void prestar(Publicacion pub, String socio, int dias) {
prestamos.add(new Prestamo(pub, socio, dias));
System.out.println("✓ Prestado: " + pub.getInfo() + " a " + socio);
}
public void devolver(String tituloPub) {
prestamos.removeIf(p -> p.getPublicacion().getTitulo().equals(tituloPub));
System.out.println("✓ Devuelto: " + tituloPub);
}
public void listarActivos() {
System.out.println("\n📚 Préstamos activos en " + nombre + ":");
if (prestamos.isEmpty()) {
System.out.println(" (ninguno)");
}
for (Prestamo p : prestamos) {
System.out.println(" " + p);
}
}
}
// Programa principal
public class BibliotecaApp {
public static void main(String[] args) {
Biblioteca bib = new Biblioteca("Biblioteca Municipal");
Libro libro1 = new Libro("Clean Code", "Robert C. Martin", 2008, 464);
Libro libro2 = new Libro("Design Patterns", "GoF", 1994, 395);
Revista rev1 = new Revista("IEEE Software", "IEEE", 2025, 42);
bib.prestar(libro1, "Ana García", 14);
bib.prestar(libro2, "Carlos López", 7);
bib.prestar(rev1, "Ana García", 3);
bib.listarActivos();
bib.devolver("Clean Code");
bib.listarActivos();
}
}
Salida esperada:
✓ Prestado: Clean Code (Robert C. Martin, 2008) a Ana García
✓ Prestado: Design Patterns (GoF, 1994) a Carlos López
✓ Prestado: IEEE Software (IEEE, 2025) a Ana García
📚 Préstamos activos en Biblioteca Municipal:
Libro: Clean Code (Robert C. Martin, 2008) → Socio: Ana García (devolver antes de 2026-03-07)
Libro: Design Patterns (GoF, 1994) → Socio: Carlos López (devolver antes de 2026-02-28)
Revista: IEEE Software (IEEE, 2025) → Socio: Ana García (devolver antes de 2026-02-24)
✓ Devuelto: Clean Code
📚 Préstamos activos en Biblioteca Municipal:
Libro: Design Patterns (GoF, 1994) → Socio: Carlos López (devolver antes de 2026-02-28)
Revista: IEEE Software (IEEE, 2025) → Socio: Ana García (devolver antes de 2026-02-24)
extends, la composición con un List<Prestamo> dentro de Biblioteca, y la asociación con referencias entre Prestamo y Publicacion.
🌐 UML en la actualidad: relevancia y alternativas
Es importante ofrecer una visión honesta del lugar que ocupa UML en el panorama actual del desarrollo de software. UML fue el estándar dominante e indiscutido del modelado de software desde finales de los 90 hasta mediados de la primera década de los 2000. Sin embargo, su papel ha evolucionado significativamente con la llegada de las metodologías ágiles y los nuevos enfoques de desarrollo.
Dónde UML sigue siendo valioso
🔹 Proyectos empresariales grandes: Sistemas bancarios, aeronáuticos, de defensa o telecomunicaciones donde la documentación exhaustiva es un requisito regulatorio o contractual.
🔹 Enseñanza y formación: UML sigue siendo la herramienta estándar para enseñar diseño orientado a objetos en universidades y centros de formación. Aprender UML desarrolla la capacidad de pensar en abstracto sobre la estructura del software.
🔹 Comunicación entre equipos: El diagrama de clases y el de secuencia son universalmente comprendidos y siguen siendo la forma más rápida de explicar una arquitectura en una pizarra o en una revisión de diseño.
🔹 IA y sistemas complejos: Los proyectos de inteligencia artificial y machine learning, que involucran arquitecturas cada vez más complejas, están redescubriendo el valor de los diagramas UML para documentar pipelines de datos y flujos de procesamiento.
Donde UML ha perdido terreno
🔹 Equipos ágiles pequeños: En equipos de 3-8 personas que trabajan con Scrum o Kanban, el coste de mantener diagramas UML formales actualizados suele superar el beneficio. Estos equipos prefieren diagramas informales en pizarras o herramientas ligeras.
🔹 Microservicios y cloud-native: La arquitectura de microservicios requiere modelar la comunicación entre servicios a un nivel que UML no aborda de forma nativa.
Alternativas y complementos modernos
| Alternativa | Enfoque | Cuándo usarla |
|---|---|---|
| Modelo C4 | 4 niveles jerárquicos (contexto, contenedores, componentes, código) | Documentar arquitectura de forma progresiva |
| PlantUML | Genera diagramas UML desde texto plano | Integrar diagramas en repositorios de código |
| Mermaid | Diagramas desde texto en Markdown | Documentación en GitHub, GitLab, wikis |
| BPMN | Notación específica para procesos de negocio | Modelar workflows y procesos empresariales |
🛠️ Herramientas para crear diagramas UML
Existen numerosas herramientas para crear diagramas UML, desde aplicaciones de escritorio profesionales hasta herramientas web gratuitas y generadores basados en texto:
| Herramienta | Tipo | Precio | Ideal para |
|---|---|---|---|
| Visual Paradigm | Desktop / Web | Gratis (Community) / Pago | Modelado profesional completo |
| StarUML | Desktop | Pago (con trial) | Desarrollo profesional con generación de código |
| draw.io (diagrams.net) | Web / Desktop | Gratis | Diagramas rápidos, integración con Google Drive |
| PlantUML | Texto → Diagrama | Gratis (open source) | Diagramas versionables en repositorios Git |
| Mermaid | Texto → Diagrama | Gratis (open source) | Documentación en Markdown, GitHub, wikis |
| Lucidchart | Web | Freemium | Colaboración en equipo en tiempo real |
| IntelliJ IDEA | Plugin IDE | Incluido en Ultimate | Diagramas directamente desde código Java |
⚠️ Errores frecuentes con UML
Error 1: Confundir agregación y composición
Muchos diseñadores usan agregación (◇) y composición (◆) indistintamente. La diferencia clave es el ciclo de vida: en la composición, las partes no existen sin el todo. Si eliminas un Pedido, sus LineasPedido se eliminan con él (composición). Pero si cierras un Departamento, los Empleados siguen existiendo (agregación).
// COMPOSICIÓN: LineasPedido se crean y mueren con el Pedido
public class Pedido {
private List<LineaPedido> lineas = new ArrayList<>();
public void agregarProducto(String producto, int cantidad) {
lineas.add(new LineaPedido(producto, cantidad)); // Se crea aquí
}
// Al eliminar el Pedido, las líneas desaparecen
}
// AGREGACIÓN: Empleados existen independientemente del Departamento
public class Departamento {
private List<Empleado> empleados = new ArrayList<>();
public void asignar(Empleado emp) {
empleados.add(emp); // El empleado ya existía antes
}
// Si se elimina el departamento, los empleados siguen existiendo
}
Error 2: Intentar modelar todo con UML
Uno de los errores más comunes es querer crear diagramas UML de absolutamente todo el sistema antes de empezar a codificar. Esto genera una documentación enorme, difícil de mantener y que rápidamente queda desactualizada. La recomendación profesional es modelar solo los aspectos críticos o complejos del sistema: la arquitectura general, los flujos principales y las entidades del dominio.
Error 3: Usar UML como decoración sin propósito
Cada diagrama UML debe tener un propósito claro: comunicar una decisión de diseño, validar una arquitectura o documentar una interacción compleja. Si un diagrama no aporta información que no sea obvia leyendo el código, probablemente no sea necesario.
✏️ Ejercicios resueltos
Ejercicio 1: De UML a Java — Sistema de figuras geométricas
Enunciado: Dado el siguiente diagrama de clases UML, implementa las clases en Java y crea un programa que calcule el área de cada figura.
┌──────────────────────┐
│ «abstract» │
│ Figura │
├──────────────────────┤
│ - color: String │
├──────────────────────┤
│ + calcularArea(): dbl│
│ + getDescripcion() │
└──────────┬───────────┘
┌────┴────┐
△ △
┌──────────┐ ┌──────────────┐
│ Circulo │ │ Rectangulo │
├──────────┤ ├──────────────┤
│- radio │ │- ancho │
│ │ │- alto │
├──────────┤ ├──────────────┤
│+calcArea │ │+calcArea │
│+getDesc │ │+getDesc │
└──────────┘ └──────────────┘
▶ Ver solución
abstract class Figura {
private String color;
public Figura(String color) {
this.color = color;
}
public abstract double calcularArea();
public String getDescripcion() {
return getClass().getSimpleName() + " (" + color + ") — Área: "
+ String.format("%.2f", calcularArea());
}
}
class Circulo extends Figura {
private double radio;
public Circulo(String color, double radio) {
super(color);
this.radio = radio;
}
@Override
public double calcularArea() {
return Math.PI * radio * radio;
}
}
class Rectangulo extends Figura {
private double ancho;
private double alto;
public Rectangulo(String color, double ancho, double alto) {
super(color);
this.ancho = ancho;
this.alto = alto;
}
@Override
public double calcularArea() {
return ancho * alto;
}
}
public class TestFiguras {
public static void main(String[] args) {
Figura[] figuras = {
new Circulo("rojo", 5.0),
new Rectangulo("azul", 4.0, 6.0),
new Circulo("verde", 3.0)
};
for (Figura f : figuras) {
System.out.println(f.getDescripcion());
}
}
}
Salida:
Circulo (rojo) — Área: 78,54
Rectangulo (azul) — Área: 24,00
Circulo (verde) — Área: 28,27
Ejercicio 2: Identificar relaciones UML
Enunciado: Para cada par de clases, indica qué tipo de relación UML es la más adecuada y justifica tu respuesta.
a) Coche y Motor
b) Estudiante y Universidad
c) ArrayList y List
d) Pedido y Cliente
e) Impresora y Documento
▶ Ver solución
a) Coche → Motor: Composición (◆). El motor se crea para un coche concreto y no tiene sentido independiente. Si se destruye el coche, se destruye su motor.
b) Estudiante → Universidad: Agregación (◇). El estudiante existe independientemente de la universidad. Si la universidad cierra, el estudiante sigue existiendo.
c) ArrayList → List: Realización (implementación de interfaz). ArrayList implementa la interfaz List en Java.
d) Pedido → Cliente: Asociación (línea continua). Un pedido está asociado a un cliente, pero son entidades independientes con sus propios ciclos de vida.
e) Impresora → Documento: Dependencia (flecha punteada). La impresora usa temporalmente un documento para imprimirlo, pero no lo almacena como atributo permanente.
Ejercicio 3: Diseñar un diagrama de clases UML
Enunciado: Diseña un diagrama de clases UML para un sistema de gestión de empleados con los siguientes requisitos:
• Hay empleados a tiempo completo y a tiempo parcial (ambos son tipos de empleado).
• Cada empleado pertenece a un departamento.
• Un departamento tiene un jefe (que es un empleado).
• Los empleados a tiempo completo tienen un salario anual.
• Los empleados a tiempo parcial tienen un precio por hora y un número de horas semanales.
• Todos los empleados pueden calcular su salario mensual.
▶ Ver solución
import java.util.ArrayList;
import java.util.List;
abstract class Empleado {
private String nombre;
private String id;
public Empleado(String nombre, String id) {
this.nombre = nombre;
this.id = id;
}
public abstract double calcularSalarioMensual();
public String getNombre() { return nombre; }
@Override
public String toString() {
return nombre + " (" + id + ") — Salario mensual: "
+ String.format("%.2f€", calcularSalarioMensual());
}
}
class EmpleadoCompleto extends Empleado {
private double salarioAnual;
public EmpleadoCompleto(String nombre, String id, double salarioAnual) {
super(nombre, id);
this.salarioAnual = salarioAnual;
}
@Override
public double calcularSalarioMensual() {
return salarioAnual / 12.0;
}
}
class EmpleadoParcial extends Empleado {
private double precioHora;
private int horasSemanales;
public EmpleadoParcial(String nombre, String id, double precioHora, int horasSemanales) {
super(nombre, id);
this.precioHora = precioHora;
this.horasSemanales = horasSemanales;
}
@Override
public double calcularSalarioMensual() {
return precioHora * horasSemanales * 4.33; // Semanas promedio al mes
}
}
class Departamento {
private String nombre;
private Empleado jefe;
private List<Empleado> empleados;
public Departamento(String nombre, Empleado jefe) {
this.nombre = nombre;
this.jefe = jefe;
this.empleados = new ArrayList<>();
this.empleados.add(jefe);
}
public void agregarEmpleado(Empleado emp) {
empleados.add(emp);
}
public void mostrarInfo() {
System.out.println("📋 Departamento: " + nombre);
System.out.println(" Jefe: " + jefe.getNombre());
System.out.println(" Empleados:");
for (Empleado e : empleados) {
System.out.println(" " + e);
}
}
}
public class TestEmpleados {
public static void main(String[] args) {
EmpleadoCompleto jefe = new EmpleadoCompleto("María López", "E001", 48000);
EmpleadoCompleto dev = new EmpleadoCompleto("Pedro Ruiz", "E002", 36000);
EmpleadoParcial becario = new EmpleadoParcial("Laura Gil", "E003", 12.0, 20);
Departamento ingenieria = new Departamento("Ingeniería", jefe);
ingenieria.agregarEmpleado(dev);
ingenieria.agregarEmpleado(becario);
ingenieria.mostrarInfo();
}
}
Salida:
📋 Departamento: Ingeniería
Jefe: María López
Empleados:
María López (E001) — Salario mensual: 4000,00€
Pedro Ruiz (E002) — Salario mensual: 3000,00€
Laura Gil (E003) — Salario mensual: 1039,20€
❓ Preguntas frecuentes sobre UML: Lenguaje Unificado de Modelado
Las dudas más comunes respondidas de forma clara y directa.
💬 Foro de discusión
¿Tienes dudas sobre UML: Lenguaje Unificado de Modelado? Comparte tu pregunta con la comunidad.
Todavía no hay mensajes. ¡Sé el primero en participar!