Configurando Claude Code para un proyecto de iOS
Esta app se llamó después Deep Cut Atlas. Aquí se llama “Discoverer” en todo el texto, porque así se llamaba el día que pasó esto.
Llevo tiempo queriendo construir una app de iOS. La idea se llama Discoverer: encuentra lanzamientos nuevos de artistas en mi biblioteca de Apple Music que todavía no he agregado, para no quedarme atrás en música que sí me importa. Premisa sencilla, pero MusicKit + SwiftData + CloudKit + StoreKit es un stack real que hay que conectar.
Empecé creando el repositorio de git y escribiendo un CLAUDE.md. En mis proyectos de homelab y del blog, CLAUDE.md se ha vuelto el archivo más importante del repositorio: ahí defino las reglas de cómo los agentes que trabajan en el código deben comunicarse conmigo. Para esos proyectos las reglas son sobre todo de infraestructura: no ejecutar comandos destructivos sin preguntar, siempre revisar los archivos de estado antes de asumir algo, ese tipo de cosas.
Para un proyecto de iOS las preocupaciones son distintas. iOS tiene bordes filosos con los que un agente de propósito general se va a topar si no tiene un mapa.
MusicKit no funciona en el simulador. Nunca. Acceso a la biblioteca, búsqueda en el catálogo, reproducidos recientemente, escritura en playlists: todo requiere un iPhone físico con una suscripción activa de Apple Music. Si voy a dejar que un agente escriba código contra MusicKit, necesita saber esto desde el inicio, o voy a terminar con código que no se puede probar sin que me dé cuenta de por qué. El enfoque estándar es definir un protocolo (MusicLibraryServiceProtocol) con una implementación real y una simulada, inyectada según el entorno para que los builds del simulador usen la simulada automáticamente.
CloudKit cambia las reglas de SwiftData. SwiftData solo local permite propiedades no opcionales sin valores por defecto. CloudKit no lo permite: falla al inicializar el esquema. Cada propiedad de un @Model necesita ser opcional o tener un valor por defecto. Es el tipo de cosa que uno descubre a las malas en su primer proyecto con CloudKit.
La configuración sandbox de StoreKit solo carga desde el IDE de Xcode. Ejecutar xcodebuild test desde la terminal no la carga. Todas las pruebas de compras de StoreKit tienen que pasar por Xcode y luego Run. Esto importa porque dependo de xcodebuild para builds sin interfaz vía xcbeautify, pero cualquier trabajo de StoreKit necesita una sesión completa de Xcode.
@Observable, no ObservableObject. Swift 6 + SwiftUI trajo @Observable como el patrón correcto. Los agentes entrenados antes de que este patrón se estabilizara van a usar ObservableObject por defecto. Una regla explícita evita ese desvío.
Todo esto entró al CLAUDE.md como “restricciones críticas”, no como comentarios de arquitectura sino como reglas obligatorias que los agentes deben seguir en cada tarea. La regla de proyecto de aprendizaje vino primero, eso sí: los agentes deben explicar cada comando antes de ejecutarlo, interpretar la salida después, y no asumir que conozco los detalles internos de MusicKit o StoreKit. El homelab es un proyecto de aprendizaje. Este también.
La configuración real del repositorio tomó unos veinte minutos: git init, tomar la plantilla de .gitignore de Swift de GitHub (y agregarle .DS_Store y DerivedData/, que la plantilla omite), escribir un README con una descripción del proyecto y pasos de configuración pendientes, y confirmar los tres archivos como el commit inicial. Luego gh repo create discoverer --private --push para crear el repositorio en GitHub y subirlo en un solo paso.
El repositorio es privado hasta que la app esté lista para el App Store. En ese punto pasa a formar parte de la historia del lanzamiento. Por ahora es solo un lugar donde va a vivir el código, y las reglas ya están escritas.
Lo que sigue: el proyecto de Xcode en sí, con un TabView de 3 pestañas, el entitlement de MusicKit, el archivo de configuración de StoreKit, y la estructura de carpetas del CLAUDE.md. El CLAUDE.md ya está ahí, esperándolo.
Lecturas relacionadas
La lista de reproducción que ya tenía el nombre correcto
Un artefacto del cambio de nombre, una API sin campo de autor para actualizar, y una prueba en dispositivo real que demostró que el error ya se había arreglado solo, cerrado como aceptar tal cual.
Álbumes destacados, datos reales de MusicKit y una prueba que mintió al pasar
No existe un flag isEssential: lo que ves como Essentials es una playlist de canciones. featuredAlbums es la señal real, y un cambio en los fixtures expuso una prueba sin volver a correr.
La pestaña Discover que quedó atascada excavando para siempre
Una búsqueda de artista fallida nunca contaba como revisada, así que un artista problemático se reelegía para siempre - hasta que una prueba de varias rondas detectó la segunda protección.
También te podría ser útil
Proton Drive
Almacenamiento en la nube cifrado, del equipo detrás de Proton Mail.
Como socio de Proton, obtengo ingresos por las compras que califican de los servicios de privacidad y seguridad de Proton (Pass, Mail, VPN, Drive).
Más informaciónProton VPN
VPN comercial con filtrado NetShield e interruptor de apagado automático.
Como socio de Proton, obtengo ingresos por las compras que califican de los servicios de privacidad y seguridad de Proton (Pass, Mail, VPN, Drive).
Más informaciónAdGuard para iOS
Bloqueo de anuncios y rastreadores en todo el sistema en iOS, sin necesidad de un servidor DNS aparte.
Como afiliado de AdGuard, obtengo ingresos por las compras que califican.
Más información