La mayoría de los tipos de diagrama UML viven en los libros de texto. El diagrama de clases es el que los ingenieros siguen dibujando: una imagen de las cosas de las que trata un sistema, qué guarda cada una, qué puede hacer y cómo se relacionan. Bien dibujado, zanja las discusiones sobre el modelo de dominio antes de que nadie escriba una migración o una API.
Este artículo construye un diagrama de clases UML para una pequeña plataforma de cursos en línea, una decisión cada vez, y explica la notación a medida que aparece: visibilidad, multiplicidad, asociación, composición, herencia e interfaces. Termina con los errores que hacen inútiles los diagramas de clases y con un vistazo rápido a cómo el diagrama se traslada al código.
Cuándo un diagrama de clases es la imagen correcta
La especificación UML 2.5.1 divide sus diagramas en diagramas de estructura y diagramas de comportamiento. Un diagrama de clases es un diagrama de estructura: muestra tipos, sus atributos y operaciones y las relaciones entre ellos, sin ninguna noción de tiempo.
| Pregunta que necesitas responder | Diagrama |
|---|---|
| Qué conceptos existen, qué saben y cómo se relacionan | Diagrama de clases |
| Cómo se almacenan los datos: tablas, claves, claves foráneas | Diagrama ER |
| Qué ocurre y en qué orden entre participantes | Diagrama de secuencia |
| Por qué estados pasa un objeto | Diagrama de máquina de estados |
Un diagrama de clases se gana su sitio para un modelo de dominio, para los tipos públicos de una biblioteca o para un diseño que estás a punto de construir. No se lo gana como volcado de todas las clases de un repositorio: eso lo muestra mejor un IDE, y la imagen queda desfasada con el siguiente commit.
La notación en dos tablas
Una clase es una caja con hasta tres compartimentos: el nombre, los atributos y las operaciones. Cualquier compartimento puede omitirse.
| Elemento | Notación | Significado |
|---|---|---|
| Clase abstracta | Nombre en cursiva, o {abstract} |
No se puede instanciar; las subclases la completan |
| Interfaz | «interface» sobre el nombre |
Un contrato de operaciones |
| Visibilidad | + public, - private, # protected, ~ package |
Quién puede usar la característica |
| Atributo | name: Type [multiplicity] = default |
Los datos que guarda el objeto |
| Operación | name(param: Type): ReturnType |
El comportamiento que ofrece el objeto |
| Característica estática | Subrayada | Pertenece a la clase, no a una instancia |
Las relaciones son líneas entre clases, y los extremos llevan la mayor parte del significado:
| Relación | Línea | Se lee como |
|---|---|---|
| Asociación | Línea continua; una punta de flecha abierta marca un extremo navegable | "está relacionado con" |
| Agregación | Línea continua, rombo hueco en el todo | "agrupa"; las partes viven por su cuenta |
| Composición | Línea continua, rombo relleno en el todo | "está hecho de"; las partes desaparecen con el todo |
| Generalización | Línea continua, triángulo hueco en el padre | "es un tipo de" |
| Realización | Línea discontinua, triángulo hueco en la interfaz | "implementa" |
| Dependencia | Línea discontinua, punta de flecha abierta en el destino | "usa"; un cambio en el destino puede romper el origen |
La composición es la estricta. Según la especificación, un objeto parte pertenece como mucho a un compuesto a la vez, y cuando se elimina el compuesto, sus partes se eliminan con él. La agregación compartida, el rombo hueco, es la ambigua: la propia especificación dice que su semántica precisa varía según el ámbito de aplicación y el modelador. Úsala solo si tu equipo se pone de acuerdo en lo que significa; una asociación simple dice lo mismo con menos debate.
Multiplicidad
La multiplicidad indica cuántos objetos pueden estar en un extremo de una asociación: 1 para exactamente uno, 0..1 para opcional, * (lo mismo que 0..*) para cualquier número, 1..* para al menos uno y rangos como 2..5. Escríbela en el extremo, junto a la clase que cuenta. Lee Course 1 — 1..* Module como "un curso tiene uno o más módulos, y cada módulo pertenece exactamente a un curso".
Las multiplicidades que faltan son la razón más habitual de que un diagrama de clases empiece discusiones en lugar de cerrarlas. En el ejemplo de abajo, todas las asociaciones tienen sus dos extremos completos.
El ejemplo: una plataforma de cursos en línea
Empieza por frases sencillas, no por cajas:
- Los instructores publican cursos. Un curso tiene uno o más instructores.
- Un curso está hecho de módulos ordenados, y un módulo de lecciones ordenadas.
- Una lección es un vídeo, un cuestionario o una tarea.
- Los cuestionarios y las tareas se califican; los vídeos no.
- Los estudiantes se inscriben en cursos. Una inscripción registra el progreso y guarda las entregas del estudiante.
- Un estudiante que completa un curso obtiene un certificado por esa inscripción.
- Los cursos se agrupan en categorías, y un curso puede estar en varias.
Usuarios: una clase, dos roles
El primer impulso es hacer Student e Instructor subclases de User. Eso se rompe la primera vez que un instructor sigue el curso de otra persona: en la mayoría de los lenguajes de programación, un objeto tiene una sola clase durante toda su vida, así que no puede ser ambas cosas ni cambiar después. Modela los roles como asociaciones: un User imparte cursos y un User tiene inscripciones. Reserva la herencia para lo que es un tipo de algo durante toda su vida, como un cuestionario que es un tipo de lección.
Composición cuando los ciclos de vida van unidos
Un módulo no significa nada fuera de su curso, ni una lección fuera de su módulo. Borrar un curso borra ambos. Eso es composición: un rombo relleno en Course y en Module, con 1 en el todo. Las categorías son distintas. Borrar la categoría "Ingeniería de datos" no debe borrar sus cursos, así que de Category a Course hay una agregación con * en ambos extremos.
Herencia más una interfaz
Lesson es abstracta; VideoLesson, Quiz y Assignment heredan su título y su posición. La calificación atraviesa ese árbol: los cuestionarios y las tareas se califican, los vídeos no, y una revisión entre pares añadida el año que viene podría calificarse sin ser una lección. Eso es una interfaz, Gradable, realizada por Quiz y Assignment. GradingService depende de Gradable, no de las clases concretas, y la flecha de dependencia discontinua dice exactamente eso.
Una asociación con datos propios
Enrollment se sitúa entre User y Course. UML tiene una notación específica para esto, la clase asociación, unida a la línea con una línea discontinua. La mayoría de los equipos la dibujan en cambio como una clase normal con dos asociaciones, porque tiene identidad, datos y comportamiento propios: la fecha de inscripción, el progreso, las entregas y como mucho un certificado.
Contenido del diagrama
- Category
- Course
- User
- Module
- Enrollment
- Certificate
- Lesson
- Submission
- VideoLesson
- Quiz
- Assignment
- Gradable
- GradingService
- groups
- teaches
- enrolls
- for
- earns
- holds
- answers
- «use»
Cómo leerlo:
- Arriba a la izquierda, el catálogo.
Coursese compone deModule, que se compone deLesson, con rombos rellenos en el todo.Categoryagrupa cursos con un rombo hueco. - Abajo a la izquierda, los tipos de lección.
Lessones abstracta.VideoLesson,QuizyAssignmentapuntan a ella con triángulos huecos. - A la derecha, el aprendizaje. Un
Userimparte cursos, con1..*instructores por curso, y tiene cualquier número de inscripciones, cada una para exactamente un curso. UnEnrollmentguarda entregas y obtiene como mucho unCertificate. - Abajo a la derecha, la calificación.
QuizyAssignmentrealizanGradablecon líneas discontinuas y triángulos huecos. CadaSubmissionresponde a un elemento calificable, yGradingServicedepende solo de la interfaz.
Errores comunes
- Todas las clases del código. Entre diez y quince clases por diagrama es un buen límite. Divide un modelo más grande por áreas o muestra el resto como nombres sin compartimentos.
- Getters y setters como operaciones. Enumera operaciones con significado de dominio, como
publish()oenroll(), no los métodos de acceso. - Tablas dibujadas como clases. Atributos como
courseIdpertenecen al diagrama ER. En un diagrama de clases, la línea de asociación es la referencia. - Herencia para roles. Si un objeto puede ganar o perder el rol, es una asociación, no una subclase.
- Rombos sin significado. Si nadie sabe decir qué le pasa a las partes al borrar el todo, usa una asociación simple.
- Detalles de implementación a nivel de dominio. Deja fuera las anotaciones del framework, los DTO y los repositorios, salvo que el diagrama trate de ellos.
Del diagrama al código
El diagrama se traslada directamente al código. En TypeScript:
interface Gradable {
maxScore(): number;
autoGrade(submission: Submission): Score | null;
}
abstract class Lesson {
constructor(public title: string, public position: number) {}
}
class Quiz extends Lesson implements Gradable {
questions: Question[] = [];
maxScore(): number {
return this.questions.length;
}
autoGrade(submission: Submission): Score | null {
return submission.scoreAgainst(this.questions);
}
}
class Course {
private readonly modules: Module[] = []; // composition: owned and removed with the course
categories: Category[] = []; // aggregation: categories exist on their own
addModule(title: string): Module {
const created = new Module(title, this.modules.length);
this.modules.push(created);
return created;
}
}
La composición se convierte en propiedad: el curso crea sus módulos, y nada mantiene vivo un módulo cuando el curso ha desaparecido. La agregación se convierte en una referencia a un objeto con su propio ciclo de vida. Cuando el código y el diagrama no coinciden, actualiza el diagrama en el mismo pull request; es mucho más fácil cuando el diagrama es un enlace y no una captura de pantalla, como se explica en diagramas de arquitectura que no se quedan obsoletos.
Constrúyelo en Archboard
- Inicia sesión con Google o GitHub y crea un diagrama.
- Del pack UML, añade una Clase por cada concepto y escribe atributos y operaciones, uno por línea. Pon el estereotipo de
Lessonenabstract. - Añade una Interfaz para
Gradablecon sus dos operaciones. - Conecta las clases. Pon el extremo de flecha del lado del todo en Rombo relleno para la composición y en Rombo para la agregación, y en Triángulo del lado del padre para la herencia. Dibuja la realización como una línea discontinua con un Triángulo en la interfaz, y la dependencia como una línea discontinua con una Flecha abierta.
- Pon las multiplicidades como texto junto a cada extremo y nombra las asociaciones, como "teaches", en la etiqueta de la conexión.
- Añade una Nota UML para las decisiones, como por qué los roles son asociaciones.
El ejemplo usa 13 nodos de diagrama, dentro del plan Free. Con un agente de IA conectado por MCP puedes describir los requisitos y dejar que esboce las clases, y después corregir tú las multiplicidades.
Preguntas frecuentes
¿Qué diferencia hay entre un diagrama de clases y un diagrama de objetos? Un diagrama de clases muestra tipos y las reglas entre ellos. Un diagrama de objetos muestra instancias concretas en un momento dado, con sus valores y enlaces, y sirve para comprobar un diagrama de clases con un ejemplo real.
¿Qué diferencia hay entre agregación y composición? Con la composición, una parte pertenece a un único todo y se borra con él. Con la agregación, las partes pueden existir por su cuenta y compartirse; su significado exacto queda a criterio del modelador, así que muchos equipos usan en su lugar una asociación simple.
¿Debe un diagrama de clases coincidir exactamente con el código? Un modelo de dominio debe coincidir con los conceptos y las relaciones, no con cada campo y cada método. Mantenlo a un nivel que puedan leer tanto una product manager como un ingeniero.
¿En qué se diferencia un diagrama de clases de un diagrama de componentes C4? Un diagrama de componentes C4 muestra bloques desplegables y sus responsabilidades. Un diagrama de clases baja un nivel más, a los tipos que hay dentro; consulta el ejemplo de diagrama del modelo C4.
¿Puedo generar un diagrama de clases automáticamente a partir del código? Muchos IDE pueden, pero el resultado muestra todas las clases y cuesta leerlo. Dibuja el modelo de dominio a mano o con un agente de IA, y limítalo a las clases que importan.