Fragua Tech

Patrones de Diseño y Arquitectura

Clase 8

GoF, MVC, Clean Architecture y el arte de no sobre-ingeniar.

Objetivos de aprendizaje

Los patrones son vocabulario compartidoy soluciones probadas. Hoy aprendes a reconocerlos, aplicarlos y —lo más importante—a no usarlos cuando no hacen falta.

🧠

Qué son

Entender el origen GoF y por qué tener vocabulario común acelera al equipo.

🔧

GoF en práctica

Aplicar Singleton, Factory, Builder, Observer, Strategy y Adapter en TypeScript.

🏗️

Arquitectura

Diferenciar MVC, MVVM y Clean Architecture y saber cuándo invertir en cada una.

⚠️

Criterio

Detectar anti-patrones y aplicar YAGNI antes de añadir capas "por si acaso".

¿Qué son los patrones de diseño?

En 1994 la Gang of Four publicóDesign Patterns: 23 soluciones a problemas recurrentes del diseño orientado a objetos. Treinta años después siguen siendo el lenguaje común del oficio.

Vocabulario compartido

"Esto es un Observer" → todo el equipo entiende sin explicar 50 líneas de código.

Soluciones probadas

No reinventas la rueda: alguien ya enfrentó tu problema, lo nombró y lo documentó.

Mantenibilidad

Quien venga después reconocerá la forma y podrá modificar sin entender todo el sistema.

Flexibilidad controlada

Permiten cambiar partes del sistema sin romper otras… cuando la necesidad es real.

Aviso: los patrones son herramientas, no objetivos. Aplícalos cuando resuelven un problema real, no porque queden bien en el CV.

Las tres categorías GoF

CategoríaQué resuelveEjemplos
CreacionalesCómo se crean los objetosSingleton · Factory · Builder · Prototype
EstructuralesCómo se componen objetos y clasesAdapter · Decorator · Facade · Composite
De comportamientoCómo interactúan y se comunicanObserver · Strategy · Command · State

Esta clase cubre los 7 patrones que probablemente uses esta semana, no los 23. El resto vive en los libros del bloque "Para profundizar".

Practica reconocer el patrón

Lee cada escenario, elige el patrón que mejor encaja y comprueba. Cubre el Ejercicio 8.1 al completo.

#1Tu app soporta múltiples métodos de pago (tarjeta, PayPal, crypto) y pueden agregarse nuevos sin modificar el código existente.

#2Tu app tiene un logger que debe ser el mismo en toda la aplicación.

#3Una API externa devuelve datos en formato diferente al que tu app espera.

#4Cuando hay una compra: enviar email, actualizar inventario, generar factura y notificar al vendedor — todo de forma independiente.

#5Necesitas construir consultas SQL con múltiples cláusulas opcionales (where, limit, order…).

Para empezar con los patrones

Tres recursos suficientes para tener una base sólida antes de leer el libro original.

Material complementario
Creacional

Singleton

Garantiza que una clase tenga una sola instancia y proporciona un punto global de acceso a ella.

class Database {
  private static instance: Database;
  private constructor() {
    console.log("Conectando a la BD...");
  }
  static getInstance(): Database {
    if (!Database.instance)
      Database.instance = new Database();
    return Database.instance;
  }
}
const a = Database.getInstance();
const b = Database.getInstance();
a === b // true

Buenos casos

Conexión a BD, logger, cache, registro de configuración cargada del entorno.

Cuidado

Es estado global disfrazado: hace tests difíciles. Si dudas, usa inyección de dependencias en su lugar.

Alternativa

En Node muchas veces basta con exportar una instancia desde un módulo: el propio sistema de módulos garantiza la unicidad.

Creacional

Factory

Centraliza la creación de objetosdetrás de una interfaz común, ocultando el new al resto del código.

interface Notificacion {
  enviar(mensaje: string): void;
}
class Email implements Notificacion { ... }
class SMS   implements Notificacion { ... }
class Push  implements Notificacion { ... }
class NotificacionFactory {
  static crear(tipo: "email"|"sms"|"push") {
    switch (tipo) {
      case "email": return new Email();
      case "sms":   return new SMS();
      case "push":  return new Push();
    }
  }
}
NotificacionFactory.crear("email").enviar("Hola");

Cuándo usarlo

Cuando la creación depende de un parámetro (tipo, config, entorno) y quieres aislarla del cliente.

Beneficio

Añadir un nuevo tipo (WhatsApp) toca solo la factory, no los 50 sitios que envían notificaciones.

Variantes

Factory Method (subclases deciden la clase concreta),Abstract Factory (familias de objetos relacionados).

Creacional

Builder

Construye objetos complejos paso a paso, separando la construcción de la representación.

class QueryBuilder {
  private tabla = "";
  private condiciones: string[] = [];
  private campos: string[] = ["*"];
  private limite: number | null = null;
  from(t: string)        { this.tabla = t;          return this; }
  select(...c: string[]) { this.campos = c;          return this; }
  where(c: string)       { this.condiciones.push(c); return this; }
  limit(n: number)       { this.limite = n;          return this; }
  build(): string { /* arma el SQL */ }
}
new QueryBuilder()
  .from("usuarios")
  .select("nombre", "email")
  .where("edad > 18")
  .limit(10)
  .build();

Cuándo brilla

Objetos con muchos parámetros opcionales: queries, requests HTTP, configuración de notificaciones.

Fluent interface

Cada método retorna this. El código se lee como una frase y el compilador valida cada paso.

Alternativa moderna

En TS muchas veces basta con un options object. Builder gana cuando hay orden, validaciones o estados intermedios.

Profundizar en creacionales

Cuidado con Singleton: tiene tantos defensores como detractores. Lee ambos lados.

Material complementario
Estructural

Adapter

Traduce una interfaz incompatible a la que tu sistema entiende, sin tocar al cliente ni al servicio adaptado.

// API legacy: devuelve XML
class APIVieja {
  obtenerDatosXML(): string {
    return "<user><n>Ana</n><e>25</e></user>";
  }
}
// Tu app espera JSON
class AdapterUsuario {
  private vieja = new APIVieja();
  obtener(): { nombre: string; edad: number } {
    const xml = this.vieja.obtenerDatosXML();
    return { nombre: parse(xml, "n"),
             edad:   +parse(xml, "e") };
  }
}

Cuándo usarlo

APIs legacy, librerías de terceros con interfaces incómodas, migraciones incrementales (mantener el cliente intacto mientras cambias el backend).

Ya lo viste en

Los dialects de Sequelize (Postgres/SQLite/MySQL) — misma API JS sobre drivers nativos distintos.

Tip de diseño

Pon los Adapters en una capa propia (adapters/) para aislar el ruido del exterior — encaja con Clean Architecture, sección siguiente.

Estructural

Decorator

Envuelve un objeto añadiendo comportamiento sin cambiar su interfaz. Apilable: varios decoradores se componen en cualquier orden.

interface Notificador {
  enviar(msg: string): void;
}
class EmailNotificador implements Notificador {
  enviar(m) { sendEmail(m); }
}
class ConLog implements Notificador {
  constructor(private wrapped: Notificador) {}
  enviar(m) {
    console.log("→ enviando:", m);
    this.wrapped.enviar(m);
  }
}
// Apilar decoradores:
const n = new ConReintentos(
  new ConLog(
    new EmailNotificador()));

Frente a herencia

La herencia fija el comportamiento en compile-time. Decorator lo compone en runtime y permite combinaciones que la jerarquía no anticipó.

Ya lo viste en

app.use(helmet()), cors(),rateLimit() en Express (Clase 7) — cada middleware decora la request. También los HOCs y custom hooks de React (Clase 5).

Decorators de TS

@Injectable(), @Controller() en NestJS/Angular — mismo nombre, misma idea: meta-decoración declarativa.

Estructural

Facade

Ofrece una interfaz simple sobre un subsistema complejo. Esconde la coreografía interna y expone solo lo que el cliente necesita.

// Subsistema complejo (varios módulos)
import { stripe }     from "./payments";
import { inventario } from "./stock";
import { facturas }   from "./billing";
import { emailer }    from "./email";
// Facade: una sola puerta de entrada
export const tienda = {
  async comprar(usuario, items) {
    const cargo = await stripe.cobrar(...);
    await inventario.descontar(items);
    const pdf = await facturas.generar(cargo);
    await emailer.enviar(usuario, pdf);
    return { ok: true, cargo };
  }
};
// El controlador solo hace:
await tienda.comprar(req.user, req.body.items);

Cuándo usarlo

Cuando un caso de uso típico requiere coordinar 4-5 módulos y todos los callers duplican esa coreografía.

Ya lo viste en

axios es una facade sobre fetch / XHR. La API de Prisma/Sequelize es una facade sobre el driver de la BD.

Facade vs Adapter

Adapter traduce una interfaz que ya existe.Facade inventa una interfaz nueva más simple sobre varias.

Cuidado

Una facade que crece sin control se convierte en God Object. Mantén una facade por caso de uso, no una por aplicación.

Profundizar en estructurales

Decorator, Facade, Adapter, Proxy y Composite componen el grupo. Estos recursos te dan ejemplos en TypeScript y comparativas entre patrones cercanos.

Material complementario
Comportamiento

Observer

Define una dependencia uno-a-muchos: cuando un objeto cambia, todos sus observadores son notificados automáticamente.

class EventEmitter {
  private listeners = new Map<string, Listener[]>();
  on(evento, cb)  { /* registra */ }
  off(evento, cb) { /* elimina */ }
  emit(evento, data) {
    this.listeners.get(evento)?.forEach(cb => cb(data));
  }
}
tienda.on("compra", e => email.enviar(e));
tienda.on("compra", e => stock.descontar(e));
tienda.on("compra", e => analytics.track(e));
tienda.emit("compra", pedido);

Lo que ganas

El emisor no conoce a sus oyentes. Añadir un nuevo listener no toca el código que dispara el evento.

Dónde lo ves cada día

DOM events (addEventListener), Node EventEmitter, RxJS, Redux store, hooks de React (useEffect + dependencias).

Riesgo

Olvidar off() al desmontar un componente → fugas de memoria y listeners zombi.

Observer en vivo

Suscribe y desuscribe listeners y dispara el evento. Verás que el emisor jamás se entera de quién está escuchando.

Listeners suscritos al evento compra:

Activa o desactiva listeners y dispara el evento. El emisor no cambia: los listeners se acoplan al canal, no al emisor.

Esperando emisión...
Comportamiento

Strategy

Encapsula algoritmos intercambiablesdetrás de una misma interfaz, para elegir uno u otro en tiempo de ejecución.

interface EstrategiaDescuento {
  calcular(precio: number): number;
}
class SinDescuento     { calcular(p) { return p; } }
class Porcentual       { /* p * (1-x/100) */ }
class BlackFriday      { /* condicional */ }
class Carrito {
  estrategia: EstrategiaDescuento;
  total() {
    return this.estrategia.calcular(this.subtotal());
  }
}

Si tu código tiene…

un switch o cadena de if/else sobre un "tipo", casi seguro está pidiendo Strategy a gritos.

Casos típicos

Descuentos, métodos de pago, algoritmos de ordenamiento, políticas de retry, formateadores de exportación.

Strategy vs State

Estructuralmente iguales. La diferencia es semántica: Strategy lo elige el cliente; State cambia por sí mismo según las transiciones.

Cambia la estrategia, no el carrito

El carrito siempre llama a estrategia.calcular(). Alternar entre cuatro estrategias diferentes no cambia ni una línea del carrito.

Carrito

  • Teclado mecánico89.00 €
  • Ratón inalámbrico35.50 €
  • Soporte monitor49.99 €
Subtotal174.49 €
Ahorro- 0.00 €
Total174.49 €

El carrito no sabe nada de descuentos: solo invoca estrategia.calcular().

Estrategia activa: Sin descuento

              class SinDescuento implements EstrategiaDescuento {
            
                calcular(p: number) { return p; }
            
              }
            

Profundizar en comportamiento

Observer y Strategy son la punta del iceberg. Command, State, Chain of Responsibility, Iterator, Mediator y Template Method completan el grupo.

Material complementario
💡

Llevas semanas usando patrones

Cada framework que vimos en las Clases 3-7 está atravesado por patrones GoF. Una vez que les pones nombre, los reconoces en todos lados — y entiendes por qué las APIs se diseñaron así.

Clase 5 — React

Observer, Composite, Decorator, Strategy, Provider/DI.

Clase 6 — Express

Chain of Responsibility, MVC, Decorator, Strategy.

Clase 7 — Sequelize / JWT / Zod / Next

Active Record, Builder, Adapter, Strategy.

Las próximas dos diapositivas son un mapa: framework → patrón → dónde lo viste.

Frontend: React, Next.js, TypeScript

Si llegaste hasta aquí, ya escribiste estos patrones decenas de veces. Ahora les pones nombre.

HerramientaPatrónDónde lo viste
useState / useReducerObserverReact se suscribe al estado y re-renderiza cuando cambia.
useEffect(deps)ObserverReactivas al cambio de cualquier valor del array de dependencias.
<Provider value={...}>Singleton + Dependency InjectionUna instancia compartida que el subárbol consume vía useContext.
<Layout><Page /></Layout>CompositeComponentes que contienen componentes; el árbol JSX es la estructura.
withAuth(Component) / hooksDecoratorAñaden comportamiento (auth, logging) sin tocar el componente original.
React.createElementFactoryEl JSX que escribiste se compila a llamadas a esta factory.
render={(item) => <Row />}StrategyRender props: el padre decide cómo pintar cada elemento.
getServerSideProps / getStaticPropsStrategyNext.js elige SSR o SSG según qué función exportes (Clase 7).
Discriminated unions en TSStrategyswitch (action.type) en un reducer = strategy tipada.
MVVM mental modelMVVMEl componente con useState es el ViewModel: estado observable que la vista renderiza.

Backend: Express, Sequelize, JWT, Zod, Node

Los frameworks del backend están aún más cargados de patrones que el frontend: APIs encadenables, middlewares y drivers intercambiables.

HerramientaPatrónDónde lo viste
app.use(middleware)Chain of ResponsibilityCada middleware decide pasar la request al siguiente con next() (Clase 6).
helmet() / cors() / rateLimit()DecoratorEnvuelven la respuesta añadiendo headers o comportamiento (Clase 7).
routes / controllers / modelsMVCLa estructura de carpetas de tu API de tareas es MVC clásico (Clase 6).
express.json() / urlencoded()StrategyParsers intercambiables del body según el Content-Type.
EventEmitter (Node)ObserverEl patrón GoF en su forma más pura: .on() / .emit().
Tarea.findAll({ where, include, limit, order })BuilderSequelize construye SQL paso a paso a partir de un objeto encadenable (Clase 7).
sequelize.define(...)Active RecordEl modelo sabe cómo persistirse: tarea.save(), tarea.destroy().
Sequelize dialects (pg, sqlite, mysql)AdapterMisma API JS para BDs distintas — Adapter sobre cada driver nativo.
new Sequelize(url) (instancia única)SingletonUna sola conexión exportada desde models/index.js.
jwt.sign(payload, secret, { algorithm })StrategyHS256, RS256, ES256… algoritmos de firma intercambiables (Clase 7).
bcrypt vs argon2 vs scryptStrategyMisma interfaz (hash/compare), distintos algoritmos.
z.object({...}).extend(...).refine(...)Builder + CompositeZod arma schemas componiendo otros schemas con API fluida (Clase 7).
git commit / git revertCommand + MementoCada commit es un comando reversible; el repo es la historia de snapshots (Clase 3).

Cuando una API de un framework "se siente bien", suele ser porque aplica el patrón correcto sin que tengas que pensarlo.

Fundacional

Layered / N-tier

La arquitectura más antigua y todavía la más usada: el sistema se divide encapas horizontales y cada capa solo habla con la capa inmediatamente inferior.

┌──────────────────────────────┐
│  Presentation (UI / API)     │
├──────────────────────────────┤
│  Business / Service          │
├──────────────────────────────┤
│  Persistence / Repository    │
├──────────────────────────────┤
│  Database                    │
└──────────────────────────────┘
Regla: presentation NUNCA toca
directamente persistence — pasa
por la capa de servicios.

Cuándo basta

La mayoría de las apps CRUD. Es lo que sale de la estructura del proyecto de la Clase 8 (routes/controllers/services/repositories).

Trampa típica

La capa de servicios se convierte en God Layer: meter ahí toda la lógica acaba siendo un Big Ball of Mud. Mantén dominio fuera.

Parientes

Es la base de la que parten MVC, Clean yHexagonal: todas son refinamientos sobre la idea de capas.

MVC — Modelo · Vista · Controlador

Separa la app en tres responsabilidades: datos,presentación yorquestación. La base de Express, Rails, Laravel y Spring.

// Controlador (orquesta)
const tareasController = {
  listar: async (req, res) => {
    const tareas = await Tarea.findAll();
    res.json(tareas);
  },
  crear: async (req, res) => {
    const t = await Tarea.create(req.body);
    res.status(201).json(t);
  }
};
// Rutas (vista de la API)
app.get ('/api/tareas', tareasController.listar);
app.post('/api/tareas', tareasController.crear);

Modelo

Datos y reglas de negocio. En la API de Clase 7 son los modelos Sequelize.

Vista

Lo que el usuario consume: HTML en SSR, JSON en una API, JSX en una SPA.

Controlador

Recibe la petición, llama al modelo y elige qué devolver. Debe serdelgado: la lógica vive en servicios.

Variantes

MVVM (View ↔ ViewModel, dominante en frontend reactivo).MVP. Hoy MVC en backend casi siempre se complementa con una capa de services.

Variantes de MVC: MVP y MVVM

Cuando la UI se vuelve compleja, MVC clásico se acorta. MVP yMVVM redistribuyen responsabilidades para que la vista sea más tonta y la lógica más testeable.

ConceptoMVCMVPMVVM
Quién orquestaControllerPresenterViewModel
Vista ↔ lógicaVista lee del modeloPresenter empuja a la vistaData-binding bidireccional
Vista testeableDifícilSí (vista pasiva)Muy fácil
Hábitat naturalAPIs web, Rails, ExpressAndroid clásico, WinFormsReact, Vue, WPF, SwiftUI

MVC

El controller maneja request → modelo → vista. La vista puede leer el modelo directamente. Dominante en backend.

MVP

La vista es pasiva: solo expone eventos. El presenter decide qué renderizar y llama a métodos de la vista (view.showError(...)).

MVVM

El ViewModel expone estado observable; la vista se re-renderiza sola cuando cambia. Es lo que haces cada día con useState en React.

Clean Architecture

Capas concéntricas donde las dependencias apuntan hacia adentro. La lógica de negocio no sabe que existen ni Express ni Prisma.

┌──────────────────────────────────────────────┐
│  Frameworks & Drivers (Express, Prisma, …)   │
│  ┌────────────────────────────────────────┐  │
│  │  Adaptadores (Controllers, Repos, DTO) │  │
│  │  ┌──────────────────────────────────┐  │  │
│  │  │  Casos de uso (Application)      │  │  │
│  │  │  ┌────────────────────────────┐  │  │  │
│  │  │  │  Entidades (Domain rules)  │  │  │  │
│  │  │  └────────────────────────────┘  │  │  │
│  │  └──────────────────────────────────┘  │  │
│  └────────────────────────────────────────┘  │
└──────────────────────────────────────────────┘
Regla: las flechas de dependencia apuntan
siempre hacia adentro. Lo interno NO conoce
a lo externo (DIP — Dependency Inversion).

Entidades

Reglas de negocio puras: una Tarea sabe si está vencida, sin importar quién la pintó ni dónde se guarda.

Casos de uso

Orquestan entidades para cumplir intenciones del usuario:CrearTarea, CompletarTarea.

Adaptadores

Traducen entre el mundo exterior y el interior: HTTP → caso de uso, caso de uso → SQL. (¡Aquí vive el patrón Adapter!)

Cuándo invertir aquí

Proyectos con lógica de negocio compleja y larga vida útil. Para una API CRUD pequeña, Clean Architecture es sobre-ingeniería.

Estructural

Hexagonal (Ports & Adapters)

Hermana de Clean Architecture, propuesta por Alistair Cockburn en 2005. La aplicación expone puertos (interfaces) y el mundo exterior se conecta mediante adaptersintercambiables.

        ┌────── REST adapter ──┐
        │      ┌──────────┐    │
CLI ────┼─[ port ]─│          │    │
        │          │   APP    │    │
Tests ──┼─[ port ]─│ (dominio │    │
        │          │  + casos │    │
GraphQL─┼─[ port ]─│   uso )  │    │
        │          └────┬─────┘    │
        └─[ port: Repository ]───┘
         ┌────────┼────────┐
         ▼        ▼        ▼
      Postgres  Mongo   InMemory
El dominio solo conoce puertos.
Los adapters son enchufables.

Idea central

La lógica de negocio no sabe si la entrada vino por HTTP, CLI o un test. Tampoco sabe si los datos viven en Postgres o en memoria.

Testing real

Cambiar PostgresRepository por InMemoryRepositorybasta para tener tests rápidos del dominio sin BD.

Hexagonal vs Clean

Misma intención (DIP). Hexagonal pone el foco enpuertos; Clean en capas. En la práctica se mezclan: muchos proyectos llaman "Hexagonal" a su Clean.

Monolito vs Microservicios

No es una cuestión de moda: es una decisión organizacional. Microservicios cambian un problema técnico (acoplamiento) por uno operativo (distribuir, observar, desplegar).

ConceptoMonolitoMonolito modularMicroservicios
DespliegueUn solo artefactoUn artefacto, módulos aisladosN pipelines, N entornos
Tx entre módulosACID localACID localSaga / eventual
EscaladoTodo o nadaTodo o nadaPor servicio
Coste operativoBajoBajoAlto (obs, K8s, red, …)
Equipos1–2 equipos2–5 equiposMuchos equipos autónomos

Empieza así

Monolith first (Fowler). Empieza con un monolito modular bien diseñado. Extrae servicios cuando duela, no antes.

Falacia común

"Microservicios = código más limpio". No: el código se ordena con módulos. Microservicios se justifican por escalado y autonomía de equipos.

Ley de Conway

"Las organizaciones diseñan sistemas que reflejan su estructura de comunicación". Tu arquitectura acabará pareciéndose a tu organigrama.

Event-Driven · CQRS · Event Sourcing

Patrones de arquitectura para sistemas distribuidos o de alta escritura/lectura. No son obligatorios — son herramientas cuando el dominio lo pide.

Event-Driven Architecture

Los servicios se comunican publicando eventos en un broker (Kafka, RabbitMQ, SQS) en vez de llamarse entre sí por HTTP.

OrderService
   │ publish "OrderPlaced"
┌──Kafka────┐
   │  │  │
   ▼  ▼  ▼
Email Stock Invoice

Es el patrón Observer elevado a nivel de sistema.

CQRS

Command Query Responsibility Segregation. Separa el modelo deescritura del de lectura. Cada uno se optimiza para su carga.

POST /orders ──► Write Model
                (Postgres)
        ▼ proyección
GET /orders ───► Read Model
                (ElasticSearch)

Útil cuando lees ≫ escribes (feeds, dashboards).

Event Sourcing

En vez de guardar el estado actual, guardas lasecuencia de eventos que llevó a él. El estado se reconstruye replicando eventos.

AccountOpened
MoneyDeposited(100)
MoneyWithdrawn(30)
→ saldo: 70

Auditoría perfecta gratis. Compleja para queries directas.

Regla: empieza sin nada de esto. Adóptalo solo cuando el dominio lo justifique (alta escala, auditoría legal, equipos autónomos). YAGNI también aplica aquí.

Anti-patrones y sobre-ingeniería

Conocer los patrones es la mitad. La otra mitad es sabercuándo no aplicarlos.

Anti-patrónQué esSolución
God ObjectUna clase que sabe y hace todoResponsabilidad única (SRP)
Spaghetti CodeTodo conectado con todoSeparar capas y módulos
Copy-Paste ProgrammingDuplicar en vez de abstraerExtraer funciones / DRY
Premature OptimizationOptimizar antes de medir"Make it work, make it right, make it fast"
Golden HammerMisma herramienta para todoElegir la adecuada al problema

YAGNI

You Ain't Gonna Need It. No añadas funcionalidad "por si acaso". Cuando llegue la necesidad real, llegará con información que ahora no tienes.

Regla de oro

Si dudas si necesitas un patrón, probablemente no lo necesitas. Espera a que el dolor sea real y entonces refactoriza.

Refactor en vivo: Strategy + Observer

El código Antes es el del Ejercicio 8.4: un if/else con email y analytics duplicados. Alterna a Después y observa cómo Strategy y Observer separan responsabilidades.

function procesarPago(tipo, monto, datos) {
  if (tipo === "tarjeta") {
    if (datos.numero.length !== 16)
      return { exito: false, error: "Numero invalido" };
    console.log(`Cobrando $${monto} a tarjeta ${datos.numero}`);
    console.log(`Email: Pago de $${monto} con tarjeta`);
    console.log(`Analytics: pago_tarjeta, $${monto}`);
    return { exito: true, transaccion: "TXN-" + Date.now() };
  } else if (tipo === "paypal") {
    if (!datos.email.includes("@"))
      return { exito: false, error: "Email invalido" };
    console.log(`Cobrando $${monto} via PayPal a ${datos.email}`);
    console.log(`Email: Pago de $${monto} con PayPal`);
    console.log(`Analytics: pago_paypal, $${monto}`);
    return { exito: true, transaccion: "PP-" + Date.now() };
  }
}
Ejecuta un pago para ver la traza...

En el código «Antes», añadir crypto exige editar el if/else y duplicar las llamadas a email y analytics. En el «Después» basta con crear PagoCrypto y registrarlo.

Material complementario

Resumen de la Clase 8

ConceptoDefinición
Patrón de diseñoSolución probada a un problema recurrente de diseño
GoFGang of Four — los 4 autores del libro clásico de 1994
SingletonUna única instancia con punto de acceso global
FactoryEncapsula la creación de objetos detrás de una interfaz
BuilderConstrucción paso a paso con API encadenable
ObserverNotifica automáticamente a varios oyentes ante un evento
StrategyAlgoritmos intercambiables en tiempo de ejecución
AdapterTraduce interfaces incompatibles sin tocar al cliente
MVCSeparación Modelo · Vista · Controlador
Clean ArchitectureCapas concéntricas con dependencias hacia adentro
YAGNINo implementes lo que aún no necesitas
Básico

Ejercicio 8.1: Identificar patrones

Cinco escenarios, un patrón por cada uno. Usa el interactivo de la sección 2 para autoevaluarte; abajo tienes la rúbrica.

Ver solución
  1. Strategy — cada método de pago es intercambiable.
  2. Singleton — una sola instancia del logger.
  3. Adapter — convierte el formato externo al interno.
  4. Observer — múltiples listeners independientes.
  5. Builder — query construida paso a paso.
Intermedio

Ejercicio 8.2: Implementar un Factory

Crea un sistema de formas geométricas usando Factory.

Requisitos

  • • Interfaz Forma con area(), perimetro(), describir()
  • • Implementa Circulo, Rectangulo, Triangulo
  • FormaFactory.crear(tipo, dimensiones) instancia la forma correcta
  • • Lanza error claro si el tipo no existe

Bonus

  • ⭐ Añadir Cuadrado sin tocar nada salvo la factory
  • ⭐ Validar dimensiones con Zod antes de construir
  • ⭐ Tests con Vitest para cada forma
Intermedio

Ejercicio 8.3: Observer en la práctica

Sistema de notificaciones para una tienda online. Tres eventos, tres listeners independientes.

Eventos

  • usuario:registro
  • pedido:creado
  • pedido:enviado

Listeners

  • EmailService
  • AnalyticsService
  • InventarioService

Simula el flujo completo: registro → compra → envío. Comprueba que cada listener se ejecuta solo cuando le corresponde.

Avanzado

Ejercicio 8.4: Refactorizar con patrones

El interactivo de la sección 6 contiene el código original. Identifica al menos dos patrones aplicables y reescríbelo.

Ver pista

Hay un if/else sobre un "tipo" que crece linealmente: olor aStrategy.

Las llamadas a email y analytics se duplican en cada rama: olor a Observer.

🏗️

Proyecto: refactor con arquitectura

Toma la API de tareas de las Clases 6-7 y refactorízala aplicando patrones y separación de responsabilidades. Misma funcionalidad, mejor estructura.

Objetivo

  • • Capas claras: routes / controllers / services / repositories
  • • Al menos 2 patrones de diseño aplicados
  • • API funciona idénticamente a la versión anterior
  • • Cero sobre-ingeniería

Patrones candidatos

  • Repository para aislar Sequelize
  • Strategy para política de orden/filtro
  • Observer para hooks post-creación
  • Factory para el cliente HTTP de tests

Estructura sugerida

Cada carpeta tiene una sola razón para cambiar.

src/
├── routes/         (rutas Express)
├── controllers/    (HTTP → caso de uso)
├── services/       (lógica de negocio)
├── repositories/   (acceso a BD)
├── middleware/     (auth, validar, errors)
└── utils/          (helpers puros)

controllers/

Delgados. Solo extraen datos del request, llaman al service y formatean la respuesta.

services/

Donde vive la lógica de negocio. No tocan Express ni Sequelize directamente.

repositories/

Único punto que habla con Sequelize. Cambiar de Sequelize a Prisma toca solo esta carpeta (¡eso es el patrón Adapter en acción!).

Criterios de evaluación

Checklist

  • ☑ Separación clara en capas
  • ☑ Al menos 2 patrones aplicados correctamente
  • ☑ API se comporta idéntica a la versión anterior
  • ☑ Código más legible y mantenible
  • ☑ No hay sobre-ingeniería gratuita
  • ☑ README justifica los patrones elegidos

Bonus

  • ⭐ Tests por capa con doubles (mocks de repository)
  • ⭐ Inyección de dependencias real (no Singletons)
  • ⭐ Manejo centralizado de errores con middleware
  • ⭐ ADR (Architecture Decision Record) en docs/
📚

Para profundizar

Bibliografía mínima si quieres construir criterio propio sobre patrones y arquitectura. No leas todo: elige uno por categoría.

📖 Libros

  • Design Patterns — GoF
  • Head First Design Patterns — Freeman
  • Clean Architecture — Robert C. Martin
  • A Philosophy of Software Design — Ousterhout
  • Refactoring (2.ª ed.) — Fowler
  • Domain-Driven Design — Eric Evans

🎥 Videos y charlas

  • • Okhravi — Design Patterns (playlist)
  • • Uncle Bob — The Clean Architecture
  • • Sandi Metz — All the Little Things
  • • Mary Poppendieck — The Tyranny of the Plan
  • • Greg Young — 8 Lines of Code

🔗 Sitios y referencias

  • • refactoring.guru
  • • sourcemaking.com
  • • martinfowler.com
  • • blog.cleancoder.com
  • • herbertograca.com (Software Architecture Chronicles)

💻 Código y práctica

  • kamranahmedse/design-patterns-for-humans
  • torokmark/design_patterns_in_typescript
  • • Refactoring Katas — Emily Bache
  • • exercism.io — track de TypeScript
  • jasonrudolph/programming-motherf**ker (currículo libre)
Material complementario
🎯

Tienes vocabulario nuevo

Saber nombrar lo que ya hacías es la mitad del oficio. La otra mitad es sabercuándo no aplicar nada. En la próxima clase llevamos todo esto a un proyecto integrador end-to-end.

Fragua Tech — Clase 8