Reglas de Negocio y Entidades
Las Reglas de Negocio Críticas existirían incluso sin un ordenador — las Entidades encapsulan estas reglas y datos, formando el núcleo más estable del sistema, independiente de la UI y la persistencia.
Por qué importa
Existen dos tipos de reglas de negocio. Las Reglas de Negocio Críticas son las reglas que existirían aunque el software no existiera — un banco calcula intereses de préstamos independientemente de si el sistema funciona en libros de papel o servidores en la nube. Estas reglas son lo más valioso del sistema. Deben estar aisladas, protegidas e intocadas por cualquier otra cosa. Las Reglas de Negocio de Aplicación son reglas específicas de casos de uso que solo existen porque hay software — flujos de validación, pasos de transformación de datos, lógica de orquestación.
Las Entidades son los objetos que encarnan las Reglas de Negocio Críticas. Una Entidad es un concepto de negocio puro: un Préstamo, un Pedido, una Factura. Contiene datos y las reglas que operan sobre esos datos. No sabe nada de bases de datos, interfaces de usuario, frameworks ni HTTP. Puede instanciarse en un archivo de prueba de Python sin más importaciones que la biblioteca estándar. Si una Entidad importa SQLAlchemy, ha sido corrompida. El framework ha invadido la parte más estable y valiosa del sistema.
✗El problema
A Loan class that has calculate_interest() but also save_to_db(), to_html(), and send_email_notification() — the critical business rule is buried with technical concerns it should never know about.
Bad
from sqlalchemy import Column, Float, Integer
from sqlalchemy.ext.declarative import declarative_base
import smtplib
from decimal import Decimal
Base = declarative_base()
class Loan(Base):
__tablename__ = "loans"
id = Column(Integer, primary_key=True)
principal = Column(Float)
rate = Column(Float)
term = Column(Integer)
def calculate_interest(self) -> float:
# Critical business rule — would exist on a paper ledger too
return self.principal * self.rate * self.term
def save_to_db(self, session):
session.add(self)
session.commit()
def to_html(self) -> str:
interest = self.calculate_interest()
return f"<p>Loan {self.id}: interest = {interest:.2f}</p>"
def send_email_notification(self, recipient: str):
smtp = smtplib.SMTP("localhost")
smtp.sendmail("bank@co.com", recipient,
f"Interest: {self.calculate_interest():.2f}")
# The critical rule is correct but corrupted.
# To test calculate_interest: SQLAlchemy, DB session, and smtplib must exist.
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
import nodemailer from "nodemailer";
@Entity()
export class Loan {
@PrimaryGeneratedColumn() id!: number;
@Column("float") principal!: number;
@Column("float") rate!: number;
@Column("int") term!: number;
calculateInterest(): number {
return this.principal * this.rate * this.term;
}
toHtml(): string {
return `<p>Loan ${this.id}: interest = ${this.calculateInterest().toFixed(2)}</p>`;
}
async sendEmailNotification(recipient: string): Promise {
const t = nodemailer.createTransport({ host: "localhost", port: 25 });
await t.sendMail({ from: "bank@co.com", to: recipient,
subject: "Loan interest", text: `${this.calculateInterest()}` });
}
}
// To test calculateInterest: TypeORM decorators require DB setup.
// The Entity has been corrupted by three technical concerns.
✓La solución
A pure Loan entity with only the critical business rules — no ORM, no HTML, no email. Persistence, presentation, and notification are separate concerns handled by separate classes.
Good
from decimal import Decimal
from dataclasses import dataclass
@dataclass
class Loan:
principal: Decimal
rate: Decimal # annual interest rate, e.g. Decimal("0.05") for 5%
term: int # years
def calculate_interest(self) -> Decimal:
"""Critical Business Rule — would be calculated on paper without software."""
return self.principal * self.rate * self.term
def monthly_payment(self) -> Decimal:
"""Another critical rule — depends only on principal, rate, and term."""
monthly_rate = self.rate / 12
n = self.term * 12
if monthly_rate == 0:
return self.principal / n
return self.principal * monthly_rate / (1 - (1 + monthly_rate) ** -n)
# Test with plain Python — zero infrastructure:
# loan = Loan(Decimal("10000"), Decimal("0.05"), 2)
# assert loan.calculate_interest() == Decimal("1000.00")
# Technical concerns are separate, isolated classes:
# class SqlLoanRepository(LoanRepository): ... — persistence
# class LoanPresenter: def to_html(loan: Loan) -> str: ... — presentation
# class LoanNotifier: async def send(loan: Loan, email: str): ... — notification
# The Entity knows none of them exist.
// Pure domain entity — no imports from any framework or library
export class Loan {
constructor(
readonly principal: number,
readonly rate: number, // annual interest rate, e.g. 0.05 for 5%
readonly term: number, // years
) {}
calculateInterest(): number {
// Critical Business Rule — exists in banking regardless of software
return this.principal * this.rate * this.term;
}
monthlyPayment(): number {
const monthlyRate = this.rate / 12;
const n = this.term * 12;
if (monthlyRate === 0) return this.principal / n;
return this.principal * monthlyRate / (1 - Math.pow(1 + monthlyRate, -n));
}
}
// Test with zero dependencies:
// const loan = new Loan(10_000, 0.05, 2);
// expect(loan.calculateInterest()).toBe(1000);
// Technical concerns live elsewhere:
// class TypeOrmLoanRepository implements LoanRepository { ... } — persistence
// class LoanPresenter { toHtml(loan: Loan): string { ... } } — presentation
// class LoanNotifier { async send(loan: Loan, email: string) } — notification
// The Entity imports nothing. Framework upgrades cannot corrupt it.
💡Conclusión clave
Una Entidad es un concepto de negocio que existe independientemente de la tecnología. Si importa un framework, ha sido corrompida. La Entidad es la capa más interna del sistema — no debe saber nada de bases de datos, interfaces de usuario ni servicios externos. Todo lo demás depende de ella. Ella no depende de nada.
🔧 Algunos ejercicios pueden tener errores. Si algo parece incorrecto, usa el botón Feedback (abajo a la derecha) para reportarlo — nos ayuda a corregirlo rápido.
Pista: Una Entidad es un concepto de negocio que existe independientemente de la tecnología. Si importa un framework, ha sido corrompida.
✗ Tu versión