Una guía completa para usar OCL en UML con ejemplos de PlantUML
Introducción

Mientras que UML (Lenguaje Unificado de Modelado) es excepcional para visualizar elestructurade un sistema, carece de la precisión necesaria para expresar complejasreglas de negocio y semánticas. Aquí es donde entra elLenguaje de Restricciones de Objetos (OCL) entra en juego. OCL actúa como el «pegamento» formal que vincula los diagramas UML con una lógica de negocio estricta e inequívoca.
Esta guía explica cómo OCL se integra con UML, los conceptos clave que necesita conocer y proporciona ejemplos prácticosPlantUML ejemplos para visualizar estas restricciones.
1. La sinergia: cómo OCL complementa a UML
Piense en UML como el «esqueleto» de su sistema (clases, asociaciones, estados) y en OCL como el «sistema nervioso» (las reglas, condiciones y lógica que rigen cómo se comporta el esqueleto).
Representación visual en UML
En UML estándar, las restricciones de OCL se representan típicamente utilizandoNotas adjuntas a los elementos del modelo relevantes (clases, operaciones o estados).
-
Stereotipo:A menudo, las notas que contienen OCL reciben el
{restricción}esteriotipo. -
Contexto: Toda expresión OCL comienza con una
contextopalabra clave, que declara qué elemento UML se aplica la regla.
2. Conceptos clave de OCL en UML
A. Invariantes de clase (inv)
Las invariantes son condiciones que deben siempre ser verdaderas para cada instancia de una clase, independientemente de qué operaciones se llamen.
Concepto clave: Si se viola una invariante, el sistema se encuentra en un estado inválido.

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #FFF9C4
class BankAccount {
- accountNumber: String
- balance: Real
- isOverdraftEnabled: Boolean
+ withdraw(amount: Real)
+ deposit(amount: Real)
}
note right of BankAccount
**Invariantes de clase:**
context BankAccount
-- El saldo nunca puede ser negativo a menos que el sobregiro esté habilitado
inv balanceRule:
self.balance >= 0 or self.isOverdraftEnabled
-- El límite de sobregiro no puede exceder 500
inv overdraftLimit:
self.isOverdraftEnabled implica self.balance >= -500
end note
@enduml
B. Contratos de operación (pre, post, cuerpo)
Las operaciones (métodos) pueden tener restricciones que definen su comportamiento antes y después de su ejecución.
-
Precondición (
pre): Lo que debe ser verdadero antes cuando comienza la operación. (Responsabilidad del llamador). -
Condición post- (
post): Lo que será verdadero después cuando finaliza la operación. (La garantía de la operación). -
Cuerpo (
cuerpo): Define el valor exacto de retorno para las operaciones de consulta.

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #E8F5E9
class ShoppingCart {
- items: Bag<Product>
- totalAmount: Real
+ checkout(): Receipt
+ getTotal(): Real
}
note right of ShoppingCart
**Contratos de operación:**
-- Condición previa: El carrito no debe estar vacío
context ShoppingCart::checkout(): Receipt
pre: self.items->notEmpty()
-- Condición post: El carrito se vacía y se genera un comprobante
post: self.items->isEmpty() and result <> null
-- Cuerpo: Definición de operación de consulta
context ShoppingCart::getTotal(): Real
body: self.items->collect(price)->sum()
end note
@enduml
C. Valores derivados e iniciales (derivar, init)
En lugar de almacenar datos redundantes, UML permite marcar atributos como {derivado} o {inicial}, con OCL que define su valor exacto.

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #E3F2FD
class Employee {
- firstName: String
- birthDate: Date
- {derived} age: Integer
- {initial} isActive: Boolean = true
}
note right of Employee
**Valores derivados e iniciales:**
-- Calcular la edad dinámicamente
context Employee::age: Integer
derive: self.birthDate.yearsSinceToday()
-- Estado inicial al crear
context Employee::isActive: Boolean
init: true
end note
@enduml
D. Guardas de Máquina de Estados
En los diagramas de máquina de estados, las transiciones pueden tenerguardas—expresiones booleanas que deben evaluarse como verdaderas para que ocurra la transición. OCL es el lenguaje estándar para escribir estas guardas.

@startuml
skinparam stateBackgroundColor #F3E5F5
[*] --> Created
Created --> PendingApproval : submit()
state PendingApproval {
}
PendingApproval --> Approved : [total < 10000]
PendingApproval --> Rejected : [total >= 10000]
Approved --> Shipped : ship()
Shipped --> [*]
note right of PendingApproval
**Invariante de estado:**
context Order
inv: self.status = 'Pending' implica
self.submittedBy->notEmpty()
end note
note right of Approved
**Condición posterior de approve():**
context Order::approve()
post: self.status = 'Approved' y
self.approvedDate <> null
end note
@enduml
3. Estudio de caso completo: El modelo de la empresa
Aplicaremos OCL a un diagrama de clases UML complejo que involucraEmpleado, Departamento, yProyecto. Esto demuestra cómo OCL navega por asociaciones y utiliza iteradores de colecciones (select, forAll, size).

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #FFFDE7
skinparam noteBorderColor #FBC02D
class Employee {
- SSN: String
- firstName: String
- birthDate: Date
- hireDate: Date
- salary: Float
+ age(): Integer
}
class Department {
- number: Integer
- name: String
- locations: Set<String>
- {derived} nbrEmployees: Integer
}
class Project {
- number: Integer
- name: String
- location: String
}
Employee "0..*" -- "1" Department : worksFor > department
Employee "0..*" -- "0..*" Project : worksOn > project
Employee "1" -- "0..1" Department : manages > managedDept
Department "1" -- "0..*" Project : controls > projects
Employee "1" -- "0..*" Employee : supervisor > subordinates
note right of Employee
**Invariantes complejos y navegación:**
context Employee
-- 1. Regla básica de atributo
inv ageCheck: self.age() >= 18
-- 2. Navegación de asociaciones (multiplicidad)
inv maxProjects: self.worksOn->size() <= 4
-- 3. Uso de iteradores (forAll)
-- El supervisor debe haber sido contratado antes que los subordinados
inv hireOrder: self.subordinates->forAll(
sub | sub.hireDate > self.hireDate
)
-- 4. Regla de asociación cruzada
-- Un empleado solo puede trabajar en proyectos controlados por su departamento
inv projectControl:
self.worksFor.controls->includesAll(self.worksOn)
-- 5. Identificador único (clave)
inv uniqueSSN: Employee.allInstances()->forAll(e1, e2 |
e1 <> e2 implica e1.SSN <> e2.SSN
)
end note
note bottom of Department
**Atributo derivado:**
context Department::nbrEmployees: Integer
derive: self.worksFor->size()
end note
note right of Project
**Invariante de proyecto:**
context Project
-- La ubicación del proyecto debe ser una de las ubicaciones del departamento
inv validLocation:
self.controls.locations->includes(self.location)
end note
@enduml
4. Técnicas avanzadas de OCL en UML
A. Usandolet para legibilidad
Cuando una expresión de OCL se vuelve demasiado compleja, utilice el letpalabra clave para definir variables locales dentro de la restricción. Esto hace que la nota de UML sea mucho más fácil de leer.

@startuml
skinparam classAttributeIconSize 0
class Employee {
- worksOn: Set<Project>
}
note right of Employee
**Usando 'let' para lógica compleja:**
context Employee
-- Calcular horas totales y verificar límites
inv workingHours:
let totalHours: Integer =
self.worksOn->collect(hours)->sum()
in totalHours >= 30 y totalHours <= 50
end note
@enduml
B. Manejo de valores anteriores en postcondiciones (@pre)
Al definir una postcondición, a menudo necesitas comparar el estado nuevo estado con el viejo estado. OCL utiliza el @pre postfijo para acceder al valor de una propiedad exactamente como era antes de que comenzara la operación.

@startuml
skinparam classAttributeIconSize 0
class Company {
- employees: Set<Employee>
+ hireEmployee(p: Employee)
}
note right of Company
**Usando @pre en postcondiciones:**
context Company::hireEmployee(p: Employee)
-- El nuevo conjunto de empleados es el conjunto anterior más la nueva persona
post: self.employees = self.employees@pre->including(p)
end note
@enduml
5. Mejores prácticas para OCL en UML
-
Nombra tus restricciones: Siempre da un nombre a tus invariantes y condiciones (por ejemplo,
inv maxProjects:). Esto facilita referirse a ellos en documentación o matrices de trazabilidad. -
Usa notas de forma estratégica: En PlantUML y herramientas visuales de UML, no acumules OCL en la caja de la clase. Coloca el OCL en notas adyacentes. Esto mantiene el diagrama limpio y legible.
-
No sobrecargues con restricciones: Escribe OCL solo para reglas que no se pueden expresar fácilmente mediante multiplicidades de UML (por ejemplo,
0..1,1..*) o tipos básicos. Si una regla puede mostrarse visualmente, muéstrala visualmente. -
Aprovecha los iteradores de colecciones: Domina
->select(),->forAll(),->exists(), y->collect(). Son las herramientas más potentes para navegar asociaciones de UML en OCL. -
Verifica nulos/conjuntos vacíos: Al navegar asociaciones opcionales (multiplicidad
0..1o0..*), siempre usa->notEmpty()o->isEmpty()antes de acceder a propiedades para evitar errores de evaluación no definidos.














