wandres.dev
MIGRAR A TCA · una app existente

Señales de que TCA no es para tu equipo

Después de veintinueve niveles defendiendo una arquitectura, esta lección hace lo contrario y con el mismo rigor: enumera las condiciones bajo las cuales adoptar TCA es la decisión equivocada. Analiza qué cuesta realmente la curva de aprendizaje y por qué el obstáculo no es la sintaxis sino la inversión del control; calcula el punto de equilibrio entre el coste fijo por feature y el beneficio que crece con el tamaño y la vida del proyecto; y examina el papel de la rotación, que importa menos que la existencia de alguien que sostenga el conocimiento. Termina con las seis señales de abandono y con la ruta alternativa que conserva la mayor parte del beneficio.

⏱ 20 min

Una guía que solo sabe decir que sí no es una guía, es publicidad. Llevas veintinueve niveles viendo por qué el flujo unidireccional, el estado como valor y las dependencias explícitas resuelven problemas reales, y todo eso sigue siendo verdad; lo que no se sigue de ahí es que TCA sea la decisión correcta para tu equipo, tu app y tu año. Una arquitectura no se evalúa por su elegancia interna sino por el ajuste entre lo que exige y lo que la organización puede sostener, y ese ajuste depende de variables que no aparecen en ningún repositorio: cuánta gente hay, cuánto tiempo se queda, cuántos años va a vivir el producto y quién enseñará esto cuando el que lo trajo se marche. Esta lección trata de esas variables y de cómo decir que no sin haber perdido el tiempo.

🎯 Al terminar esta lección sabrás
  • Distinguir la dificultad real de la curva —la inversión del control— del obstáculo aparente, que es la sintaxis.
  • Estimar el punto de equilibrio entre el coste fijo por feature y el beneficio que crece con el tamaño y la vida del proyecto.
  • Evaluar la rotación por su efecto sobre la custodia del conocimiento y no por su porcentaje.
  • Reconocer las seis señales de abandono y elegir la ruta alternativa que conserva la mayor parte del beneficio.

Lo que cuesta de verdad la curva

La curva de TCA no está donde la gente cree. La sintaxis se aprende en una tarde: un State, una Action, un Reducer, un Store. Lo que cuesta semanas es la inversión del control, es decir, interiorizar que no puedes hacer las cosas aquí. Quien viene de escribir un método que llama a la red, actualiza una propiedad y navega a la siguiente pantalla se encuentra con que ninguna de esas tres cosas se hace en el sitio donde se le ocurren: la llamada se devuelve como valor, la propiedad se cambia en el reducer al recibir una acción y la navegación es un dato. El momento en que alguien dice que quiere hacer una cosa sencilla y no le dejan es el momento crítico de la adopción, y es donde se decide si esa persona aprenderá la arquitectura o la saboteará escribiendo efectos con mutaciones dentro.

El aprendizaje ocurre en tres tramos con perfiles muy distintos, y confundirlos lleva a estimar mal. El primero, de unos días, es mecánico: reconocer las piezas y copiar la forma de una feature existente. El segundo, de dos a cuatro semanas, es el de la inversión del control, y es el único que puede fallar del todo. El tercero, de meses, es el de las decisiones de diseño: cuándo dividir un reducer, qué acciones son delegate, dónde poner una dependencia. Ese tercero no bloquea la productividad y se adquiere revisando código ajeno, pero es el que separa una app en TCA de una app que solo lo parece.

Con acompañamiento y revisiones de código serias, la cifra realista para una persona con experiencia media es de dos a cuatro semanas hasta ser productiva y de tres a seis meses hasta escribir TCA sin fricción. Sin acompañamiento la cifra no converge: se produce una capa de código que tiene la forma de TCA y la semántica de MVVM, con reducers que llaman a servicios directamente y estados que se mutan desde efectos, que es peor que no haber migrado porque paga todo el coste y no compra ninguna garantía.

ℹ️
La señal de que alguien todavía no ha cruzado

Pregunta cómo haría una operación nueva. Si la respuesta empieza por qué función llamar y dónde ponerla, aún está fuera. Si empieza por qué le pasa al estado y qué hecho hay que representar, ya cruzó. La transición no es gradual: es un cambio de pregunta, y ocurre un martes cualquiera.

Tamaño y vida del proyecto

El cálculo es sencillo de plantear y muy revelador. TCA impone un coste aproximadamente fijo por feature: modelar el estado, enumerar las acciones, escribir el reducer, declarar las dependencias, componer y testear. Ese coste no baja mucho con la experiencia porque no es tiempo perdido, es trabajo de diseño. El beneficio, en cambio, no es fijo: crece con el número de estados que interactúan entre sí, con el número de personas que tocan el mismo código y, sobre todo, con los años que el producto seguirá vivo, porque casi todo lo que TCA compra —reproducibilidad, verificación, composición— se cobra al volver a tocar algo.

Perfil del proyecto Coste relativo Beneficio esperado Recomendación
Prototipo o campaña, una persona, meses Alto Casi nulo No adoptar
App mediana, dos o tres personas, años Medio Medio Adoptar por partes
App grande, equipo estable, larga vida Medio Muy alto Adoptar sin dudar
App grande con rotación alta y sin custodio Alto Bajo o negativo No adoptar todavía

Hay un caso que engaña y conviene nombrarlo: la app mediana con muy poca lógica de estado, esencialmente una lista, un detalle y un formulario sobre una interfaz de red. Es grande en pantallas y minúscula en dominio, y ahí TCA cobra el coste por pantalla y no encuentra apenas complejidad que dominar. El indicador que separa a esta de una app que sí lo necesita no es el número de pantallas sino cuántos estados pueden ser simultáneamente verdaderos y contradecirse.

Hay una variante del cálculo que merece hacerse aparte, porque es la que decide en la mayoría de los casos intermedios: la fracción de la app que es concurrente. Una aplicación cuyos problemas son de disposición y de red sencilla se beneficia poco; una donde conviven descargas que se pisan, temporizadores, reconexiones, sincronización en segundo plano y cancelaciones al navegar se beneficia enormemente, porque casi todo lo que TCA impone —efectos como valores, cancelación por identificador, relojes inyectados— existe precisamente para domar eso. Si al listar tus defectos abiertos encuentras muchos con las palabras a veces, en ocasiones o solo si vas rápido, el dominio es concurrente aunque el producto no lo parezca.

La rotación y quién custodia el conocimiento

La lectura habitual dice que con mucha rotación no conviene una arquitectura exigente, y es una verdad a medias que conduce a decisiones malas. La uniformidad de TCA ayuda al incorporarse: todas las features tienen la misma forma, el sitio donde ocurre cada cosa es predecible y no hay que aprender la interpretación particular de MVVM que cada equipo inventó. El problema no es la rotación en sí, es el factor de autobús del conocimiento arquitectónico.

Si hay una sola persona capaz de explicar por qué ese Scope está ahí, por qué el efecto se cancela con ese identificador o por qué la acción es delegate y no interna, la arquitectura no es del equipo sino de esa persona, y el día que se vaya el resto seguirá copiando formas sin entenderlas. La degradación es rápida y tiene un aspecto reconocible: reducers que crecen sin dividirse, acciones que replican los métodos de un servicio, tests desactivados. Antes de adoptar conviene responder a una pregunta concreta: si la persona que más sabe de esto se fuese en tres meses, quién revisaría los cambios. Si no hay respuesta, el trabajo previo no es migrar sino formar a la segunda persona.

Existe además una asimetría que juega a favor de TCA y que conviene tener presente para no exagerar el riesgo. En una base de código MVVM sin convenciones, incorporarse cuesta porque hay que aprender la interpretación local de la arquitectura, que nadie escribió y que varía entre pantallas; en una base de código TCA disciplinada, incorporarse cuesta porque hay que aprender una arquitectura difícil, pero esa arquitectura está documentada fuera de la empresa, tiene ejemplos públicos y es la misma en todos los sitios donde se usa. La primera dificultad no es transferible y hay que pagarla entera en cada equipo; la segunda se paga una vez en la carrera de una persona. Con rotación alta, eso importa más de lo que parece, y explica por qué la respuesta correcta a la rotación no es siempre bajar el listón.

⚠️
La rotación mata la arquitectura solo si el conocimiento no estaba escrito

Convenciones acordadas, revisiones que explican en vez de aprobar y una feature ejemplar que se usa como referencia valen más que cualquier documento. Si nada de eso existe, cualquier arquitectura con opiniones se degradará; TCA solo lo hace de forma más visible, porque sus reglas son explícitas y se nota cuando se rompen.

Las seis señales, y qué hacer en su lugar

Estas son las señales que, si aparecen dos o más a la vez y llevan un trimestre ahí, aconsejan detener la adopción y no insistir. Ninguna es una opinión: todas se comprueban mirando el repositorio o preguntando al equipo, y por eso sirven para tener la conversación sin que degenere en preferencias personales.

Señal Cómo se comprueba Qué indica
Nadie explica por qué el efecto se devuelve Preguntar a tres personas La inversión del control no se cruzó
Efectos que mutan objetos compartidos Buscar accesos a singletons dentro de .run Forma de TCA, semántica de MVVM
Exhaustividad desactivada en todas partes Contar apariciones de exhaustivity = .off Los tests dejaron de sostener nada
Revisiones atascadas en una persona Mirar quién aprueba los cambios La arquitectura no es del equipo
Preguntas de la forma cómo hago tal cosa Escuchar una semana de conversaciones Se copian formas sin modelo mental
No caben dos semanas de aprendizaje Mirar el plan del trimestre siguiente La condición previa no existe

La segunda señal tiene una firma inconfundible en el código, y merece la pena saber reconocerla de un vistazo, porque es el modo exacto en que una adopción se convierte en lo peor de los dos mundos.

case .aparecio:
  return .run { _ in
    let datos = try await ServicioLegado.shared.cargar()
    ServicioLegado.shared.cache = datos   // mutación fuera del reducer
    NotificationCenter.default.post(name: .datosListos, object: nil)
  }

Ahí no hay ninguna acción que represente lo ocurrido, ningún cambio de estado que un test pueda afirmar y ningún camino para reproducir el resultado. Es MVVM con la sintaxis de TCA, y paga el coste completo de la arquitectura sin comprar ni una de sus garantías. Renunciar a TCA no obliga a renunciar a sus ideas, y esta es la parte que casi nunca se dice. Las piezas más valiosas están disponibles por separado y se adoptan de una en una, con una curva mucho más suave: swift-dependencies da la inyección explícita con valores para test y preview, que es probablemente el sesenta por ciento del beneficio total; swift-navigation da la navegación dirigida por estado sin adoptar el resto; un ViewModel con @Observable puede tener su estado modelado como valor y hacer imposibles los estados inválidos, exactamente como en el Nivel 7. Un equipo que adopta estas tres cosas sobre MVVM se queda con la mayor parte del rendimiento y sin el coste de la inversión del control, y además queda en la mejor posición posible para adoptar TCA más adelante, si alguna vez lo necesita.

@Observable
@MainActor
final class DetalleModelo {
  private(set) var estado: Carga<Pedido> = .inicial   // el estado, modelado como valor
  @ObservationIgnored @Dependency(\.pedidos) var pedidos

  func aparecio() async {
    estado = .cargando
    estado = await Result { try await pedidos.detalle(id) }.aCarga()
  }
}

Ahí no hay Store, ni reducer, ni acciones, y sin embargo hay tres de las cuatro propiedades que llevas veintinueve niveles persiguiendo: la dependencia es explícita y sustituible en un test, el estado es un valor con casos exhaustivos que hacen imposible mostrar datos y error a la vez, y la clase se puede instanciar sin interfaz. Lo único que falta es la garantía de que toda mutación tenga nombre, que es exactamente lo que cuesta la curva. Poner ese precio sobre la mesa, en vez de esconderlo detrás de una recomendación categórica, es lo que permite decidir con la cabeza fría.

🧗

La curva es conceptual

No es la sintaxis, es dejar de hacer las cosas donde se te ocurren. Dos a cuatro semanas con guía.

⚖️

Coste fijo, beneficio creciente

Se paga por feature y se cobra por año. Poca vida o poco dominio, y no llega al equilibrio.

🧑‍🏫

Factor de autobús

La pregunta no es cuánta rotación hay, sino quién revisará esto si se va el que lo trajo.

🧩

Las piezas por separado

Dependencias explícitas y navegación como estado sobre MVVM. La mayor parte del premio, poca curva.

flowchart TD
A[Evaluar la adopcion] --> B[Hay dominio con estados que se contradicen]
B -- no --> M[MVVM con estado modelado como valor]
B -- si --> C[La app vivira varios anos]
C -- no --> M
C -- si --> D[Hay quien custodie el conocimiento]
D -- no --> P[Adoptar solo dependencias y navegacion]
D -- si --> T[Adoptar TCA por flujos]
style T fill:#a6e3a1,color:#11111b
style P fill:#f9e2af,color:#11111b
style M fill:#89b4fa,color:#11111b
La corrección de una arquitectura no es una propiedad del código, sino del acoplamiento entre el código y la organización que debe sostenerlo

Es tentador tratar la elección de arquitectura como un problema técnico con respuesta objetiva, de modo que basta con razonar bien para llegar a la mejor, y quien elige otra o no ha razonado o no ha entendido. Esa forma de pensar produce evangelistas y produce, con más frecuencia de la que se admite, migraciones que dejan una app peor que la que había: con la forma de una arquitectura buena, sin sus garantías, y con un equipo que ya no confía en las decisiones de diseño porque la última le costó un trimestre y no le devolvió nada. La observación que corrige ese error es vieja y sigue siendo incómoda: una arquitectura es un contrato sobre cómo se toman las decisiones dentro de un sistema, y un contrato solo vale si hay alguien que pueda cumplirlo. TCA es un contrato exigente y muy bien redactado. Exige que toda mutación tenga nombre, que todo efecto sea un valor devuelto, que toda dependencia sea explícita y que toda navegación sea un dato. A cambio ofrece garantías que ninguna disciplina informal alcanza, porque las verifica el compilador y los tests en vez de la buena voluntad. Pero esas garantías se evaporan por completo en cuanto el contrato deja de cumplirse, y a diferencia de un enfoque flexible, TCA no degrada con elegancia: una app medio migrada, con reducers que mutan singletons y tests desactivados, es estrictamente peor que la misma app en MVVM honesto, porque paga la ceremonia entera y no compra ninguna de las promesas. De ahí se sigue el criterio que debería gobernar la decisión y que casi nunca aparece en las comparativas: no preguntes cuál arquitectura es mejor, pregunta cuál es el contrato más exigente que tu organización puede cumplir de forma sostenida durante los próximos tres años, contando con las personas que se van a ir y con las que aún no han llegado. La respuesta a esa pregunta es específica de cada equipo, cambia con el tiempo y no admite ser copiada de una conferencia. Saber decir que no cuando la respuesta es no, después de haber estudiado a fondo lo que se está rechazando, es la forma más alta de dominio técnico que hay: el conocimiento sirve, sobre todo, para saber cuándo no aplicarlo.

⚔️ Haz la evaluación honesta antes de comprometerte
  1. Estima la vida restante del producto en años y cuenta cuántos estados de tu app pueden ser simultáneamente verdaderos y contradecirse. Si el segundo número es bajo, ya tienes tu respuesta.
  2. Pregunta a tres personas del equipo cómo modelarían una funcionalidad nueva. Clasifica cada respuesta según empiece por qué función llamar o por qué le pasa al estado.
  3. Responde por escrito quién revisaría los cambios de arquitectura si la persona que más sabe se marchase en tres meses. Si no hay nombre, ese es el trabajo pendiente.
  4. Repasa las seis señales sobre tu proyecto actual, con evidencia concreta para cada una. Dos señales sostenidas durante un trimestre bastan para detener la adopción.
  5. Si el veredicto es que no, adopta solo swift-dependencies en una pantalla y mide en un mes cuánto beneficio te dio. Es la comparación que convierte una opinión en un dato.