Módulo 3: Patrones para el comportamiento que varía

1. Presentación del módulo: separa lo que cambia de lo que no

Descripción

Al terminar esta lección vas a tener tres cosas. Primero, el principio que ordena toda esta familia de patrones y que en el fondo es una sola frase: identifica qué parte de tu código cambia y aíslala de la que no cambia. Segundo, vas a conocer de cerca el material central del módulo: el fragmento del checkout de Boletia que decide el precio con un if por cada tipo de boleto, ese que crece cada vez que el negocio inventa una promoción. Y tercero, vas a tener el mapa de los tres patrones de la familia —Strategy, Template Method y State— con la pregunta exacta que distingue a cada uno, para que cuando lleguen en las lecciones siguientes ya sepas dónde vive cada uno.

Esto importa porque el problema que esta familia resuelve es, con diferencia, el más común de todos. Piensa en cualquier sistema que hayas tocado: en algún lugar hay un condicional que empezó con dos ramas, luego tuvo tres, y hoy tiene siete y nadie quiere abrirlo. No es un problema de programadores descuidados. Es lo que pasa naturalmente cuando un negocio crece: cada caso nuevo llega un martes, con prisa, y la forma más rápida de meterlo es agregar un elif donde ya estaban los otros. Nadie decide construir un monstruo; el monstruo se construye solo, un elif a la vez.

Ahora bien, vienes del módulo 2, y el módulo 2 te dijo algo que aparentemente empuja en la dirección contraria: que cada abstracción se paga, que no abstraigas con dos casos, que el error más caro es el que se comete por adelantarse. Eso sigue siendo cierto y no lo vamos a contradecir. Lo que hace este módulo es darte las herramientas con el freno ya instalado. Vas a aprender a extraer comportamiento variable, sí, pero también vas a aprender —en la lección 7, que es la vacuna explícita del módulo— que un if de dos ramas que no va a crecer está perfectamente bien como está. El objetivo no es que salgas extrayendo condicionales. Es que salgas sabiendo cuáles.

Conexión con el módulo: esta lección es el marco y el punto de partida. Aquí no vas a implementar todavía ningún patrón; vas a instalar el principio que los tres comparten y a conocer el código que se va a desarmar. La lección 2 presenta Strategy con su anatomía completa —el contrato, las implementaciones, y quién elige— y dice de frente cuánto cuesta. La lección 3 es el caso trabajado grande: las cuatro reglas de precio de Boletia convertidas en PricingRule intercambiables, paso a paso, con lo que se gana y lo que se paga. La lección 4 trae Template Method con el exportador de reportes: cuando el orden es fijo y solo algunos pasos cambian. La lección 5 trae State con el Order que se comporta distinto según esté pendiente, pagada, cancelada o reembolsada. La lección 6 es la que mantiene honesta a la familia: en Python, una Strategy suele ser una función, no una jerarquía de clases. La lección 7 es la vacuna: las señales reales de que un condicional pide extracción, frente a las falsas. Y la lección 8 —el proyecto— te entrega el checkout completo y te pide decidir cuáles de sus condicionales merecen extraerse y cuáles no, justificando también las decisiones de no tocar nada.

El taladro y sus brocas

Piensa en un taladro. Tiene un motor, un gatillo, una batería, un mandril que sujeta cosas y un mecanismo que hace girar lo que esté sujeto. Todo eso es caro de fabricar, complicado por dentro y siempre igual: no importa si vas a perforar madera, concreto o metal, el motor gira igual.

Lo que sí cambia es la punta. Para madera va una broca helicoidal, para concreto una de tungsteno con percusión, para metal una de acero rápido, y si en vez de perforar quieres atornillar, le pones una punta de destornillador y el mismo taladro se convierte en otra herramienta.

Fíjate en lo que hizo quien diseñó el taladro. No fabricó cuatro taladros distintos —uno para madera, uno para concreto, uno para metal, uno para tornillos—. Tampoco metió los cuatro comportamientos dentro del mismo aparato con un selector gigante. Lo que hizo fue encontrar dónde estaba la diferencia, sacarla del cuerpo de la herramienta, y dejar en su lugar un enchufe estándar: el mandril. El mandril no sabe qué broca tiene puesta. Solo sabe sujetar algo que tenga la forma acordada y hacerlo girar.

Esa es, literalmente, toda la idea de este módulo. El mandril es el contrato. Las brocas son las implementaciones. Y la decisión de qué broca ponerle es un tercer asunto, separado de los otros dos, que normalmente toma quien está usando el taladro y no el taladro mismo.

Ahora, y esto también viene del taladro: fíjate que el mandril cuesta algo. Un taladro con la broca soldada al eje sería más simple, más barato y probablemente más resistente. El mandril agrega piezas, agrega un punto donde algo puede aflojarse, y agrega un paso —hay que cambiar la broca— que un taladro de broca fija no tiene. La pieza intercambiable se paga. Se paga poco y vale la pena cuando de verdad hay cuatro brocas; sería absurdo si solo existiera una.

Esa frase —"se paga poco y vale la pena cuando de verdad hay cuatro"— es el módulo 2 hablando dentro del módulo 3. La vamos a repetir bastante.

Ejemplo trabajado: el checkout de Boletia y su if por tipo de boleto

Vamos al código. Este es el fragmento de checkout/checkout.py que decide cuánto cuesta un boleto. Es el material central del módulo y el que vas a desarmar en el proyecto de la lección 8. Léelo sin prisa; no hace falta que lo entiendas todo a la primera, pero sí que sientas la textura.

# Archivo: checkout/checkout.py
# El corazón de Boletia. Hoy son ~300 líneas. Este es solo el pedazo del precio.

def calculate_line_price(ticket, order, customer, now):
    """Devuelve cuánto se cobra por UN boleto dentro de una orden."""

    if ticket.kind == "general":
        price = ticket.base_price
        # Compra grupal: a partir de 10 boletos aplica 5% de descuento.
        # (lo pidió Ventas en marzo)
        if order.quantity >= 10:
            price = price * 0.95
        return round(price, 2)

    elif ticket.kind == "vip":
        price = ticket.base_price * 1.35
        # El VIP incluye acceso al lounge; se cobra como cargo fijo aparte
        # para que aparezca desglosado en la factura.
        price = price + 150.0
        # Los socios del club pagan 10% menos sobre el total del VIP.
        if customer.is_member:
            price = price * 0.90
        return round(price, 2)

    elif ticket.kind == "early_bird":
        event = get_event(ticket.event_id)
        cutoff = parse_date(event.early_bird_cutoff)
        # Si la compra ocurre antes de la fecha de corte, 30% de descuento.
        # Después del corte, el early-bird simplemente cuesta lo mismo que
        # el general (así se evita tener que despublicar los boletos).
        if now <= cutoff:
            price = ticket.base_price * 0.70
        else:
            price = ticket.base_price
        return round(price, 2)

    elif ticket.kind == "courtesy":
        # Las cortesías no se cobran, pero hay un tope por evento
        # para que un organizador no regale el aforo entero.
        issued = count_courtesies(ticket.event_id)
        if issued >= 50:
            raise TooManyCourtesies(f"El evento {ticket.event_id} ya emitió 50 cortesías")
        return 0.0

    else:
        raise ValueError(f"Tipo de boleto desconocido: {ticket.kind}")

Cuarenta líneas. No es dramático todavía, y esa es justamente la trampa: nunca es dramático todavía. Vamos a mirar lo que hay aquí con calma, porque cada observación va a reaparecer durante todo el módulo.

Observación 1: hay una pregunta y cuatro respuestas. La pregunta es siempre la misma: "¿cuánto cuesta este boleto?". Las respuestas son cuatro, y son de verdad distintas —no distintas en un número, distintas en su lógica—. La de early-bird consulta una fecha del evento. La de cortesía consulta cuántas se han emitido y puede fallar. La de VIP mira si el cliente es socio. La de general mira la cantidad de la orden. No es que cambie una constante: cambia el razonamiento completo.

Observación 2: cada rama usa datos distintos. Mira los parámetros de la función: ticket, order, customer, now. Ninguna rama los usa todos. General usa ticket y order. VIP usa ticket y customer. Early-bird usa ticket y now, más una consulta a la base de datos. Cortesía usa ticket y otra consulta. La firma de la función es la unión de lo que necesitan todas, que es una forma elegante de decir que a cada rama le sobran la mitad de sus argumentos. Ese detalle nos va a doler en la lección 3 y vale la pena que lo tengas presente desde ahora.

Observación 3: el archivo donde vive esto es el peor lugar posible. checkout/checkout.py es el corazón del sistema. Ahí pasa el cobro. Ahí se emite el boleto. Si alguien se equivoca ahí, no se rompe un reporte: se cobra mal, o no se cobra. Y sin embargo, cada vez que Marketing inventa una promoción —"boleto de estudiante", "paquete de temporada", "preventa para socios"— alguien tiene que abrir este archivo, encontrar el lugar exacto entre los elif, y meter un caso nuevo sin romper los otros cuatro.

Observación 4: probar una regla sola es imposible. Si quieres verificar que el descuento de early-bird se calcula bien, no puedes llamar solo a esa parte. Tienes que llamar a calculate_line_price completa, con un ticket cuyo kind sea exactamente "early_bird", con un order, un customer y un now que en realidad no importan pero hay que inventar igual, y confiar en que el if te lleve por la rama que quieres. La prueba de una regla arrastra el contexto de todas.

Qué esperar de este código. Vamos a poner números a lo que "crecer" significa, con una advertencia: son números de ejemplo, no una medición. Supón que Boletia agrega dos tipos de boleto al año, que es un ritmo normal para una plataforma en crecimiento. En tres años esta función tiene diez ramas y unas cien líneas. Cada una de esas diez veces alguien tocó checkout.py. Cada una de esas diez veces hubo que volver a probar el checkout completo, porque el cambio ocurrió dentro de la función que orquesta el cobro. Y cada una de esas diez veces la probabilidad de romper algo existente subió un poco, porque las ramas comparten el mismo espacio de nombres, la misma firma y las mismas variables locales.

El punto no es que el if sea feo. El punto es más concreto y más caro: agregar un caso nuevo obliga a modificar código que ya funcionaba. Cuando ves esa frase en tu cabeza al leer un condicional, estás viendo la señal real. Cuando solo ves "hay un if con varias ramas", todavía no estás viendo nada —y la lección 7 se dedica entera a esa distinción.

Una última cosa antes de seguir, porque es importante que no salgas de aquí con una idea equivocada: este código no está mal escrito. Está claro, tiene comentarios que explican el porqué, y hace lo que dice. Quien lo escribió no cometió ningún error de oficio. Simplemente escribió la solución correcta para dos casos, y el negocio le trajo cuatro. Vamos a mejorarlo, pero sin la fantasía de que quien lo escribió no sabía.

El eje de variación: la pregunta que ordena todo el módulo

Hay una pregunta que conviene que te acostumbres a hacer, porque es la que activa todo lo demás:

¿Qué cambia aquí, y qué se queda igual?

Al qué cambia lo vamos a llamar el eje de variación. Es el nombre de la dimensión a lo largo de la cual el comportamiento se mueve. En el código de arriba, el eje de variación es el tipo de boleto: es lo que hace que la respuesta sea distinta. Todo lo demás —que haya que redondear a dos decimales, que el resultado sea un número, que se calcule un boleto a la vez— es lo que se queda igual.

Identificar el eje suena trivial cuando ya te lo dijeron. No lo es cuando estás frente al código. Mira el mismo fragmento otra vez y date cuenta de que ahí dentro hay más de un eje:

Eje candidatoDónde se ve¿Es el eje principal?
Tipo de boletoEl if ticket.kind == ... de primer nivelSí. Es el que estructura la función entera y el que crece con el negocio
Cantidad compradaEl if order.quantity >= 10 dentro de generalNo. Es una regla interna de un solo tipo, con dos ramas
Si el cliente es socioEl if customer.is_member dentro de VIPNo. Igual: dos ramas, dentro de un solo tipo
Momento de la compraEl if now <= cutoff dentro de early-birdNo. Dos ramas, dentro de un solo tipo

Cuatro condicionales en cuarenta líneas, y solo uno es el eje de variación real. Los otros tres son detalles internos de una regla concreta: no crecen, no se repiten en otro lado, y extraerlos costaría más de lo que devuelve. Si en la lección 8 alguien entrega un refactor con cuatro jerarquías de clases, hizo el trabajo al revés.

¿Cómo se reconoce el eje principal? Tres señales, y conviene memorizarlas porque son las mismas que la lección 7 va a desarrollar:

  1. Crece. Cada caso nuevo del negocio agrega una rama ahí. Si puedes contar en el historial del repositorio los commits que agregaron un elif a ese mismo condicional, ese es el eje.
  2. Se repite. El mismo condicional, con las mismas ramas, aparece en otro archivo. En Boletia, ticket.kind se compara en el precio, en el reporte de ventas, en la validación de reembolso y en el correo de confirmación. Cuatro lugares que hay que tocar al agregar un tipo. Ese síntoma tiene nombre —shotgun surgery— y lo vamos a ver con detalle en el módulo 7.
  3. Mezcla decidir con hacer. La rama no solo elige un valor: contiene el trabajo completo. Fíjate en que la rama de cortesía consulta la base de datos y puede lanzar una excepción. Eso no es una decisión, es una implementación viviendo dentro de una decisión.

Cuando las tres señales están presentes, tienes un eje de variación que merece salir. Cuando solo está una, casi siempre conviene esperar. La regla de tres del módulo 2 aplica igual aquí: con dos casos todavía no sabes cuál es el eje, y si abstraes al eje equivocado el error es más caro que el condicional que querías evitar.

Un ejemplo concreto de "el eje equivocado", porque es el error que más veces he visto: alguien mira el código de arriba, ve que tres de las cuatro ramas aplican un porcentaje, y concluye que el eje es "qué porcentaje se aplica". Entonces construye una abstracción de descuentos con un percentage configurable. Funciona precioso para general, VIP y early-bird. Y luego llega cortesía, que no aplica ningún porcentaje sino que valida un tope y devuelve cero, y no cabe. Y llega "paquete de temporada", que cobra por evento y no por boleto, y tampoco cabe. La abstracción quedó encerrada en un eje que resultó ser un accidente de los primeros tres casos.

El eje real casi nunca es cómo se calculan las cosas. Es quién decide. En Boletia el eje es el tipo de boleto, porque el tipo de boleto es el concepto del negocio que trae reglas nuevas. Cuando el eje coincide con un concepto que el negocio nombra, casi siempre acertaste.

Los tres patrones de la familia y qué varía en cada uno

Los tres patrones de este módulo resuelven la misma frase —"separa lo que cambia de lo que no"— pero responden a preguntas distintas. Esta tabla es el mapa del módulo; te va a servir de brújula en las lecciones 2 a 5.

PatrónQué varía exactamenteQué se queda fijoQuién decideDónde vive en Boletia
StrategyEl algoritmo completoLa pregunta que se hace y la forma de la respuestaQuien llama, en el momento de llamarpricing/ — cuatro formas de responder "¿cuánto cuesta?"
Template MethodAlgunos pasos de un procedimientoEl orden de los pasos, que es el mismo siempreSe decide al elegir la subclasereports/ — abrir, encabezado, filas, cerrar; solo cambia el formato
StateEl comportamiento del objetoEl objeto y su identidadEl objeto mismo, según en qué estado estémodels/order.py — pendiente, pagada, cancelada, reembolsada

Vale la pena masticar cada fila con una imagen, porque la tabla sola no se te va a quedar.

Strategy es el taladro con brocas. Hay una pregunta ("perfora esto") y varias formas de responderla, intercambiables. Quien usa el taladro elige la broca antes de apretar el gatillo. El taladro no sabe cuál tiene puesta. Esta es la que más veces vas a usar en tu vida, y por eso ocupa dos lecciones enteras —la 2 para la anatomía y la 3 para el caso completo—.

Template Method es el tour guiado. El recorrido es siempre el mismo: se sale de la plaza, se pasa por la catedral, se sube al mirador y se vuelve. Ese orden no lo cambia ningún guía, porque el orden es el tour. Lo que cambia es qué cuenta cada guía en cada parada: uno se centra en la arquitectura, otro en las historias de fantasmas, otro en la comida. La estructura la fija la empresa; el contenido de cada parada lo pone el guía. Cuando en tu código el orden de los pasos es sagrado y lo que varía es lo que ocurre dentro de un par de pasos, eso es Template Method.

State es el paquete de mensajería. El paquete es el mismo objeto de principio a fin, pero lo que puedes hacer con él depende de dónde esté. Mientras está "en tránsito" puedes redirigirlo; una vez "entregado" ya no, pero puedes iniciar una devolución; si está "devuelto" no puedes hacer ninguna de las dos. Nadie elige el comportamiento desde fuera: el paquete cambia de estado como consecuencia de lo que le va pasando, y con el estado cambia lo que acepta. Cuando en tu código un objeto responde distinto a los mismos métodos según una bandera interna, eso es State.

La diferencia entre Strategy y State confunde a casi todo el mundo la primera vez, así que la dejo dicha desde ahora en una sola frase, y la lección 5 la desarrolla: en Strategy elige quien llama; en State elige el objeto, y el estado cambia solo como consecuencia de las operaciones. Estructuralmente se parecen mucho —en los dos casos hay un contrato y varias implementaciones—. Lo que los distingue no es la forma: es quién tiene el control del cambio.

Y la diferencia entre Strategy y Template Method, también en una frase: en Strategy delegas el algoritmo entero; en Template Method conservas el algoritmo y solo delegas algunas piezas. En Strategy, quien llama no sabe qué pasos hay dentro. En Template Method, los pasos y su orden están escritos a la vista, y solo el contenido de algunos está en manos de otro.

El criterio del módulo 2 no se apaga aquí

Quiero ser explícito con esto porque es el riesgo más grande del módulo. Acabas de pasar siete lecciones aprendiendo a no abstraer, y ahora empieza el catálogo. Es tentador leer eso como "ya pasó la parte del freno, ahora viene la parte divertida". No es así. El freno no se apaga; se convierte en el filtro por el que pasa cada patrón que veas de aquí en adelante.

En la práctica, esto significa que a cada patrón de este módulo le vas a hacer las mismas tres preguntas del módulo 2 antes de aplicarlo:

  • ¿Cuántas implementaciones distintas existen hoy, de verdad? No cuántas imaginas. Si son dos y la tercera es una hipótesis, la regla de tres dice que esperes. Con dos casos todavía no sabes cuál es el eje.
  • ¿Cuánto cuesta la indirección aquí? Un archivo más que abrir, un salto más al leer, un concepto más que explicarle a quien entre nuevo. En un rincón que nadie toca, ese costo es pequeño. En el corazón del checkout, donde seis personas leen todos los días, es más caro —pero también lo es el if gigante—. El costo se compara contra el costo del otro lado, nunca contra cero.
  • ¿Qué pasa si me equivoco de eje? Esta es la pregunta que casi nadie hace y la que más caro sale. Una abstracción en el eje equivocado no solo no ayuda: estorba activamente, porque cada caso nuevo hay que meterlo a la fuerza en una forma que no le queda, y con el tiempo la abstracción acumula parámetros y banderas para acomodar excepciones. Si el eje no está claro, el condicional feo es la opción reversible; la abstracción no lo es.

Fíjate en que ninguna de las tres preguntas es "¿este código se ve profesional?". La estética no entra en la decisión. Entra la probabilidad del cambio, el costo del cambio y el costo de equivocarse.

Con eso puesto, el resto del módulo se vuelve mucho más útil, porque vas a estar aprendiendo herramientas y no mandamientos.

Errores comunes

Confundir "hay un condicional" con "falta un patrón" (conceptual). Qué pasa: alguien termina este módulo y empieza a ver Strategies en todos lados. Un if que decide si mostrar el botón de descarga se convierte en una interfaz ButtonVisibilityStrategy con dos implementaciones. Una validación de dos ramas se convierte en una jerarquía. El código resultante tiene más archivos, más nombres y exactamente el mismo comportamiento, pero ahora para entender si el botón se muestra hay que abrir tres archivos. Por qué pasa: los condicionales son visibles y los patrones son la única herramienta nueva que acabas de aprender; es el síndrome del martillo nuevo, en su forma más pura. Cómo detectarlo: pregúntate cuántas ramas tenía ese condicional hace seis meses y cuántas tendrá en seis meses más. Si la respuesta es "dos, dos y dos", no hay eje de variación: hay una decisión estable. Cómo corregirlo: usa las tres señales de esta lección como filtro —crece, se repite, mezcla decidir con hacer— y exige al menos dos de las tres antes de mover una línea. La lección 7 entera está dedicada a esto.

Buscar el eje de variación en la forma del código en vez de en el negocio (de criterio). Qué pasa: alguien mira el fragmento del precio, nota que tres ramas multiplican por un factor, y abstrae "el factor" como el eje. La abstracción queda preciosa y a los tres meses no cabe el caso siguiente, así que se le agrega un parámetro; a los seis meses, una bandera; al año, un if dentro de la abstracción, que es exactamente el condicional que se quería evitar, pero ahora escondido detrás de dos archivos. Por qué pasa: la forma del código es lo que tienes delante y el negocio no está escrito en ninguna parte. Es más fácil generalizar lo que ves que preguntar de dónde vienen los casos. Cómo detectarlo: si tu abstracción tiene un nombre que un compañero de Producto no entendería (PercentageModifier, PriceTransformer), es señal de que la sacaste de la forma y no del dominio. Cómo corregirlo: nombra el eje con la palabra que usa el negocio. En Boletia, Producto dice "tipo de boleto", no "modificador de precio". PricingRule por tipo de boleto envejece bien porque el negocio seguirá inventando tipos de boleto; PercentageModifier envejece mal porque el negocio nunca prometió que todo fuera un porcentaje.

Creer que este módulo cancela el módulo 2 (de expectativa). Qué pasa: alguien lee el módulo 2 como un trámite —la parte aburrida antes del catálogo— y llega aquí con ganas de aplicar. En la primera oportunidad mete un patrón donde había dos casos, se siente productivo, y seis meses después es la persona que dejó el rincón que nadie entiende. Por qué pasa: el módulo 2 enseña a no hacer algo, y no hacer algo no se siente como progreso. Los patrones sí: producen archivos, nombres y estructura visible. Cómo detectarlo: si en tu último refactor no puedes nombrar qué renunciaste a cambio de lo que ganaste, no aplicaste criterio, aplicaste entusiasmo. Cómo corregirlo: acostúmbrate a escribir el tradeoff en el mensaje del commit o en la descripción del PR, en una línea: "Extraigo PricingRule porque son cuatro reglas con lógica distinta y el negocio agrega ~2 al año; el costo es un salto más al leer el checkout". Si no puedes escribir esa línea, todavía no tienes la decisión tomada. El proyecto de la lección 8 se evalúa exactamente sobre esa habilidad.

Ejercicios

Ejercicio 1 — Encuentra el eje de variación en tres situaciones cotidianas. Para cada una de estas tres situaciones, escribe: (a) qué parte cambia, (b) qué parte se queda igual, y (c) quién decide cuál se usa. Las situaciones: una máquina de café con cápsulas; una impresora que puede imprimir en blanco y negro, a color o en borrador; el proceso de facturar una compra en un país u otro con impuestos distintos.

Ver solución

La máquina de café. Cambia: la cápsula, es decir, el contenido y la molienda. Se queda igual: el mecanismo de perforar, calentar el agua y hacerla pasar a presión. Decide: la persona, en el momento, poniendo una cápsula u otra. Esto es Strategy en forma casi literal —el mismo mecanismo, piezas intercambiables, elección externa y en tiempo de ejecución—.

La impresora. Cambia: la calidad, el uso de tinta de color y la resolución. Se queda igual: el orden del procedimiento —recibir el documento, procesarlo, alimentar el papel, imprimir línea por línea, expulsar—. Decide: quien manda a imprimir, eligiendo un modo. Fíjate en la diferencia con el café: aquí el procedimiento completo es idéntico y solo cambia el contenido de un par de pasos. Eso se parece más a Template Method. La distinción no siempre es nítida en analogías, y está bien: lo que importa es que notes que la pregunta "¿varía el algoritmo entero o solo unos pasos dentro de un orden fijo?" tiene respuestas distintas en los dos casos.

La facturación por país. Cambia: la tasa, qué conceptos son gravables, el formato del documento y a veces si hay que reportarlo a una autoridad en tiempo real. Se queda igual: que hay una compra, un total y un comprobante. Decide: el país de la operación, que es un dato de la transacción, no una elección de quien programa. Esta es la más interesante de las tres porque muestra el caso donde el eje viene del negocio y no de una preferencia técnica. También muestra un eje que va a crecer: cada país nuevo es un caso nuevo, y ningún país se parece del todo al anterior.

Por qué funciona: las tres te obligan a separar mentalmente el mecanismo del contenido antes de tocar código. Cuando llegues a la lección 2 con esta separación ya hecha, la anatomía de Strategy te va a parecer obvia en vez de abstracta.

Ejercicio 2 — Cuenta los ejes en el código de Boletia. Vuelve al fragmento de calculate_line_price. Sin mirar la tabla de la sección "El eje de variación", enumera todos los condicionales que hay en la función y clasifica cada uno como eje principal o detalle interno. Para cada uno justifica en una línea usando las tres señales (crece / se repite / mezcla decidir con hacer).

Ver solución

Hay cuatro condicionales.

if ticket.kind == ... (el de primer nivel) — eje principal. Las tres señales están presentes. Crece: cada tipo de boleto nuevo agrega una rama. Se repite: ticket.kind también se compara en el reporte de ventas, en la validación de reembolso y en el correo de confirmación. Mezcla decidir con hacer: dentro de cada rama no hay un valor, hay una implementación completa —consultas a base de datos, excepciones, cálculos encadenados—.

if order.quantity >= 10 — detalle interno. Dos ramas, dentro de una sola rama del eje principal. No crece: el negocio no está agregando escalones de descuento cada trimestre (y si lo hiciera, la respuesta correcta sería una tabla de tramos, no una jerarquía de clases). No se repite. No mezcla: decide un factor y ya.

if customer.is_member — detalle interno. Idéntico razonamiento. Es una condición booleana estable de un solo tipo de boleto.

if now <= cutoff — detalle interno. Es la esencia misma de la regla early-bird: si la sacas, la regla se queda sin contenido. Un condicional que es la lógica de una regla no es un candidato a extracción; es la regla.

El punto del ejercicio: cuatro condicionales, un solo candidato. La proporción —uno de cuatro— es bastante representativa de lo que encontrarás en código real. Si tu instinto fue marcar tres o cuatro como candidatos, ahí está el sesgo que la lección 7 va a corregir.

Por qué funciona: la habilidad que vale no es "detectar condicionales", que la tiene cualquier buscador de texto. Es descartar los que no lo son. Ese descarte es el criterio.

Ejercicio 3 — Escribe el tradeoff antes de tener el patrón. Sin implementar nada todavía, escribe en tres a cinco líneas la justificación que darías en un PR para extraer las reglas de precio de Boletia. Tiene que incluir: qué ganas, qué pierdes, y bajo qué condición no lo harías. Guárdala: en la lección 3 vas a compararla con la implementación real y en la lección 8 vas a escribir varias de estas.

Ver solución

No hay una redacción única, pero una respuesta completa se parece a esto:

Extraigo las cuatro reglas de precio a objetos PricingRule separados. Gano que agregar un tipo de boleto sea crear un archivo nuevo en vez de editar checkout.py, que es el archivo más delicado del sistema; y que cada regla se pueda probar sola, con sus propios datos, sin construir una orden completa. Pierdo un salto de lectura —para saber cuánto cuesta un VIP ya no basta abrir el checkout— y sumo cinco archivos donde había una función. No lo haría si solo hubiera dos tipos de boleto o si el negocio no agregara tipos nuevos: con dos casos estables el if es más honesto y más barato de leer.

Fíjate en tres cosas de esa redacción. Primera: la ganancia está expresada como un cambio en el costo de una tarea futura concreta ("agregar un tipo") y no como un adjetivo ("más limpio", "más profesional"). Segunda: la pérdida es específica y verificable —cinco archivos, un salto más—, no un "algo de complejidad". Tercera: la condición de no hacerlo es lo que convierte la justificación en criterio; sin ella tienes una venta, no una decisión.

Si tu versión no tenía la tercera parte, vuelve a escribirla con ella. Es la parte que el proyecto de la lección 8 evalúa con más peso, y es también la que distingue en una entrevista a quien aplica patrones de quien decide sobre ellos.

Por qué funciona: escribir el tradeoff antes de conocer la implementación te obliga a razonar sobre el problema y no sobre la solución. Cuando en la lección 3 veas el código, vas a poder comparar tu predicción con lo que realmente costó, y ese contraste enseña más que cualquier explicación.

Resumen y siguiente paso

En esta lección instalaste el principio que ordena toda esta familia de patrones: separa lo que cambia de lo que no cambia. Lo viste primero en el taladro —el motor fijo, las brocas intercambiables, el mandril como contrato— con la nota importante de que el mandril cuesta algo y solo vale la pena cuando de verdad hay varias brocas.

Después conociste el material central del módulo: el fragmento de checkout/checkout.py que decide el precio con un if por tipo de boleto. Sacaste cuatro observaciones de él —una pregunta con cuatro respuestas de lógica distinta, cada rama usando datos distintos, viviendo en el archivo más delicado del sistema, e imposible de probar por partes— y llegaste a la formulación precisa del problema: agregar un caso nuevo obliga a modificar código que ya funcionaba.

Aprendiste a nombrar el eje de variación y, más importante, a distinguirlo de los condicionales que solo son detalles internos: de cuatro condicionales en cuarenta líneas, solo uno era el eje. Las tres señales para reconocerlo —crece, se repite, mezcla decidir con hacer— van a acompañarte el resto del módulo. Y viste el mapa de los tres patrones con la pregunta que separa a cada uno: Strategy cambia el algoritmo completo, Template Method cambia algunos pasos dentro de un orden fijo, State deja que el objeto cambie su propio comportamiento.

Antes de avanzar deberías poder: explicar el eje de variación de un código que estés mirando; decir en una frase qué distingue a Strategy de Template Method y de State; y nombrar las tres preguntas del módulo 2 que hay que hacerle a cualquier patrón antes de aplicarlo.

Lo que todavía no tenemos es el cómo. Sabes reconocer el problema, pero no la forma de la solución: qué piezas tiene exactamente una Strategy, quién define el contrato, quién elige la implementación y qué se paga por tenerla. La lección 2 desarma el patrón pieza por pieza y —esto es lo que casi ninguna explicación de Strategy hace— dedica una sección entera a decir cuándo el if es la mejor opción.

Recursos