El modelo mental completo: estado, datos, sistema y ciclo
Treinta y cuatro niveles de material se sostienen sobre una sola arquitectura conceptual con cuatro planos: la interfaz como proyección pura del estado, los datos como la única verdad que sobrevive al proceso, el sistema operativo como el conjunto de contratos que no negocias, y el ciclo de publicación como la capa que convierte tu código en software ajeno. Esta lección los ensambla en un único modelo y muestra que la diferencia entre los cuatro no es de tema, sino de escala temporal.
Un track largo tiene un riesgo específico: producir personas que saben mucho y no entienden nada. Se acumulan APIs —NavigationStack, @Observable, SwiftData, URLSession, WidgetKit, StoreKit— como quien colecciona herramientas sin plano, y llega el día en que hay que decidir algo que ningún tutorial cubre y no queda estructura sobre la que apoyarse. Este nivel existe para eso: para colapsar todo lo anterior en un modelo que quepa en la cabeza y que sirva para decidir. La tesis es que el desarrollo de una app en el ecosistema Apple se organiza en cuatro planos —presentación, datos, sistema y ciclo— y que lo que los separa no es el tema del que hablan sino el tiempo en el que operan: el fotograma, la sesión, el proceso y el año. Casi todos los errores graves de diseño nacen de aplicar el razonamiento de un plano en el tiempo de otro.
- Formular la ley de la presentación: la interfaz es una función del estado, no un objeto que se manipula.
- Situar la verdad en el plano de los datos y decidir con criterio qué persiste, dónde y bajo qué invariantes.
- Reconocer el plano del sistema como un conjunto de contratos —sandbox, permisos, ciclo de vida, energía— que se acatan, no se discuten.
- Integrar el ciclo de publicación en la arquitectura, entendiendo que tu código convive con versiones que ya no controlas.
Antes de recorrerlos conviene fijar la relación entre los cuatro, porque no son capas apiladas sino horizontes temporales anidados. El plano de presentación vive en el fotograma: sus decisiones se validan o se rompen en milisegundos. El de datos vive en la sesión y más allá: lo que escribes hoy lo lee tu app dentro de dos años. El del sistema vive en el proceso: el sistema operativo decide cuándo naces, cuándo te suspende y cuándo te mata. Y el del ciclo vive en el año: la versión que publicas hoy seguirá instalada cuando ya hayas reescrito tres veces esa pantalla. Un mismo bug —una lista que parpadea— puede tener su causa en cualquiera de los cuatro, y el diagnóstico empieza por saber en qué horizonte estás mirando.
La ley de la presentación: la interfaz es una proyección
La idea que atraviesa los niveles 1 a 11 tiene una formulación única: en SwiftUI no construyes una interfaz, describes una. El árbol de vistas que devuelve body no es la pantalla; es un valor, barato de crear y destruido de inmediato, que el framework compara con el anterior para calcular qué cambia de verdad. De ahí se sigue todo lo demás. No hay un label al que asignarle un texto: hay un estado del que el texto se deriva. No hay un método para ocultar una fila: hay una colección que ya no la contiene. La interfaz es una proyección del estado, y el único acto imperativo legítimo es mutar ese estado.
Esta ley tiene un corolario que cuesta más interiorizar y que explica la mitad de las rarezas del framework: si la vista es un valor, lo que la mantiene viva entre recomposiciones no es el objeto sino su identidad estructural. @State no pertenece a la estructura de la vista, pertenece al nodo de identidad que SwiftUI mantiene por debajo. Por eso una vista que cambia de posición en el árbol pierde su estado, por eso un id mal elegido en un ForEach reinicia animaciones, y por eso el nivel 11 insiste en que la pregunta correcta ante una vista que se comporta mal no es qué modificador falta sino qué identidad tiene este nodo y cuándo cambió.
// La misma pantalla, dos mentalidades opuestas.
// Imperativa: hay un objeto y le doy ordenes.
// tabla.recargar(); boton.isEnabled = false; etiqueta.text = "3 items"
// Declarativa: hay un estado y describo su proyeccion.
struct Bandeja: View {
@State private var modelo = BandejaModelo()
var body: some View {
List(modelo.tareas) { tarea in
FilaTarea(tarea: tarea)
}
.overlay { if modelo.cargando { ProgressView() } }
.task { await modelo.cargar() } // efecto atado al ciclo de la vista
}
}
El .task de ese ejemplo es la bisagra entre el plano de presentación y los demás, y merece nombrarse: es el punto donde una vista —que es pura— declara un efecto cuyo ciclo de vida queda atado a su identidad. Cuando la vista desaparece, la tarea se cancela. Ese diseño no es una comodidad, es la aplicación de la misma ley al mundo asíncrono: incluso los efectos son proyecciones de la presencia de un nodo en el árbol.
Cuando alguien pregunta cómo forzar que una vista se refresque, casi siempre está mezclando planos: intenta operar sobre la proyección en lugar de sobre la fuente. La respuesta correcta rara vez es un truco de refresco; es localizar qué dato no está siendo observado, o qué identidad está cambiando cuando no debería. Lo mismo ocurre al revés: meter lógica de red o de persistencia dentro de un body mete el horizonte de la sesión dentro del horizonte del fotograma, y el resultado es una app que recarga datos cada vez que gira el dispositivo.
El plano de los datos: dónde vive la verdad
El estado de la presentación es efímero por diseño; muere con el proceso y eso está bien. La pregunta seria es cuál es la fuente de la verdad que sobrevive, y los niveles 12 a 14 dan un mapa que se resume en una jerarquía de compromiso. UserDefaults guarda preferencias pequeñas y sin garantías fuertes. Los archivos en el contenedor guardan documentos y cachés que el sistema puede purgar si eliges mal el directorio. SwiftData o Core Data guardan el modelo de dominio con relaciones, consultas e integridad. El Keychain guarda lo que no puede filtrarse jamás. Y el servidor, cuando existe, guarda lo que debe sobrevivir a la pérdida del dispositivo.
Elegir mal en esta jerarquía produce daños que no se manifiestan hasta mucho después, y esa demora es lo que la hace peligrosa. Un token de sesión en UserDefaults funciona en todas las pruebas y es un fallo de seguridad. Una caché de imágenes en el directorio de documentos funciona hasta que el respaldo del usuario tarda veinte minutos. Un modelo sin versionar funciona hasta la primera migración en producción, momento en el cual la app deja de arrancar para quien tenía datos. Los tres errores comparten estructura: se decidieron en el horizonte del fotograma —necesito guardar esto ahora— cuando pertenecían al horizonte de los años.
// Un modelo pensado para durar: identidad estable, campos opcionales
// para el futuro y una version explicita del esquema.
@Model
final class Tarea {
#Unique<Tarea>([\.identificador])
var identificador: UUID
var titulo: String
var completada: Bool
var actualizadaEn: Date // clave para resolver conflictos
var etiquetas: [String] = [] // anadido en v2 con valor por defecto
init(titulo: String) {
self.identificador = UUID()
self.titulo = titulo
self.completada = false
self.actualizadaEn = .now
}
}
Ese actualizadaEn parece un campo administrativo y es, en realidad, una decisión arquitectónica. En el momento en que existen dos dispositivos, la verdad deja de ser un valor y pasa a ser una negociación: dos copias del mismo registro con historias distintas. Sin un criterio de resolución grabado en el modelo, la sincronización de CloudKit del nivel 23 se convierte en pérdida silenciosa de datos. La lección general es que el esquema no describe lo que la app necesita hoy, sino las preguntas que sabrás responder dentro de tres años.
El plano del sistema: contratos que no se negocian
Una app de iOS no es un programa que corre, es un invitado en un entorno que le impone condiciones. El sandbox delimita a qué puede acceder. El sistema de permisos exige pedir autorización con una justificación legible, y el usuario puede negarla para siempre. El ciclo de vida del proceso está en manos ajenas: el sistema suspende tu app cuando pasa a segundo plano y la termina sin avisar si necesita memoria. La energía y el calor imponen límites que ninguna optimización de código elude. Y las extensiones —widgets, intents, notificaciones— corren en procesos distintos con presupuestos de memoria mucho más estrechos que el de la app.
flowchart TD U[Persona usuaria toca la pantalla] --> V[Vista describe el arbol] V --> E[Estado observable] E --> V E --> D[Capa de datos: SwiftData archivos Keychain] D --> R[Red y sincronizacion] R --> D S[Sistema: sandbox permisos energia ciclo del proceso] --> V S --> D C[Ciclo: TestFlight revision versiones instaladas] --> S style V fill:#89b4fa,color:#11111b style E fill:#a6e3a1,color:#11111b style D fill:#fab387,color:#11111b style S fill:#cba6f7,color:#11111b style C fill:#f9e2af,color:#11111b
Lo importante de este plano es que sus reglas no son obstáculos que sortear sino restricciones de diseño que, aceptadas pronto, simplifican la arquitectura. Si asumes desde el primer día que tu proceso puede morir en cualquier instante, escribes código que guarda el progreso de forma incremental en lugar de al salir. Si asumes que el permiso puede ser denegado, diseñas una versión degradada de la función en lugar de un callejón sin salida. Si asumes que un widget dispone de una fracción de la memoria, no compartes con él el mismo grafo de objetos que la app. La arquitectura robusta no es la que se defiende del sistema: es la que ya está de acuerdo con él.
El plano del ciclo: el software que ya no es tuyo
El cuarto plano es el que los cursos técnicos omiten y el que separa a quien programa de quien mantiene un producto. En el instante en que una versión se publica, deja de ser código y pasa a ser una población: miles de instalaciones con estados distintos, en versiones distintas de iOS, con datos que llevan años acumulándose. No puedes desplegar un arreglo en cinco minutos como en la web; hay una revisión de por medio, una adopción gradual y un porcentaje que jamás actualizará. Todo cambio que rompe compatibilidad —de esquema, de formato de archivo, de contrato con tu propio servidor— debe diseñarse asumiendo que las dos versiones convivirán durante meses.
De ahí se derivan tres disciplinas que ya no son opcionales. La primera es el versionado explícito de todo lo que se persiste o se transmite. La segunda es la degradación por bandera: poder apagar una función en producción sin publicar una versión nueva, porque el ciclo de revisión no es un canal de emergencia. Y la tercera es la observabilidad del nivel 29: sin informes de fallo y métricas, la población es invisible y solo te enteras de los problemas por reseñas de una estrella, que es la forma más cara posible de aprender algo.
Presentación
Horizonte del fotograma. La vista es un valor, no un objeto. La identidad estructural gobierna el estado y el ciclo de los efectos.
Datos
Horizonte de los años. Una jerarquía de compromiso, del UserDefaults al Keychain. El esquema es una apuesta sobre el futuro.
Sistema
Horizonte del proceso. Sandbox, permisos, suspensión, energía, extensiones. Restricciones que se acatan y que simplifican el diseño.
Ciclo
Horizonte del año. Una versión publicada es una población heterogénea. Versionado, banderas y observabilidad dejan de ser lujos.
Mira los cuatro juntos y verás que no son cuatro temas, sino una única pregunta —dónde vive la verdad y quién puede cambiarla— repetida en cuatro escalas de tiempo, y que cada nivel del track no fue más que un ensayo de esa pregunta a una velocidad concreta. En el plano de la presentación la verdad vive en el estado y solo pueden cambiarla las acciones de la persona usuaria; la vista es una función, y por eso el pecado capital es intentar escribir sobre la proyección en vez de sobre la fuente. En el plano de los datos la verdad vive en el almacén y pueden cambiarla varios procesos y varios dispositivos; el pecado capital es tener dos fuentes que se creen ambas primarias, que es exactamente lo que produce esa clase de bug irreproducible en el que la lista muestra algo que la base de datos no contiene. En el plano del sistema la verdad vive fuera de tu programa —es el sistema operativo quien decide si sigues existiendo, si tienes permiso, si te concede memoria— y el pecado capital es escribir código que asume continuidad donde solo hay concesión revocable. Y en el plano del ciclo la verdad está distribuida entre miles de instalaciones que no controlas, y el pecado capital es tratar un despliegue como un cambio atómico cuando es una migración lenta de una población. Lo que unifica los cuatro es una regla de higiene que puedes aplicar sin conocer ninguna API: una sola fuente por hecho, y un camino explícito y observable por el que ese hecho viaja. Cuando esa regla se respeta, SwiftUI resulta casi aburrido —el estado cambia, la pantalla lo refleja—, la sincronización se vuelve tratable porque hay un criterio de resolución escrito en el modelo, el sistema deja de parecer hostil porque tu código ya asumía sus límites, y la publicación deja de dar miedo porque cada cambio lleva su versión y su interruptor. Cuando se viola, ninguna cantidad de conocimiento sobre modificadores, actores o Instruments te salvará: estarás depurando síntomas en el horizonte del fotograma de un problema que se decidió en el horizonte de los años. Esa es toda la síntesis, y es la razón por la que el resto de este nivel puede hablar de arquitectura, de mantenimiento, de oficio y de aprendizaje sin volver a explicar una sola API.
Cuatro planos, cuatro horizontes temporales. Presentación: la vista es un valor y la interfaz una proyección del estado; la identidad estructural manda sobre el ciclo del estado y de los efectos. Datos: una jerarquía de compromiso desde UserDefaults hasta el Keychain y el servidor; el esquema y su versión son decisiones a años vista. Sistema: sandbox, permisos, suspensión, energía y extensiones son contratos que, aceptados pronto, simplifican la arquitectura. Ciclo: una versión publicada es una población, no un despliegue; exige versionado, banderas y observabilidad. La regla que los une: una sola fuente por hecho y un camino explícito por el que viaja.
- Toma la pantalla más compleja de una app tuya y clasifica cada dato que muestra en efímero de presentación, persistido local, sincronizado o derivado del sistema. Marca los que aparezcan en dos categorías: ahí está tu duplicación de verdad.
- Localiza en tu código el punto exacto donde se decide la identidad de las filas de una lista y responde qué pasaría si esa identidad cambiara entre dos actualizaciones.
- Recorre tu modelo persistido y anota, para cada entidad, cómo resolverías un conflicto entre dos dispositivos. Si no sabes responder para alguna, te falta un campo.
- Escribe qué ocurre en tu app si el sistema mata el proceso justo después de que el usuario introduzca datos y antes de tocar guardar. Si la respuesta es pérdida de trabajo, tienes un supuesto de continuidad indebido.
- Elige la última función que publicaste y describe cómo la apagarías en producción esta tarde sin enviar una versión nueva a revisión.