---
title: "Lo que solo puedes hacer en nativo: WidgetKit, Live Activities, App Intents, StoreKit 2, Foundation Models y watchOS"
description: "Las capacidades de iOS que React Native no ofrece y que justifican Swift ante un cliente: WidgetKit, Live Activities, App Intents y Siri, StoreKit 2, el modelo de lenguaje en el dispositivo con Foundation Models y watchOS. Cierre de la serie y argumento de venta."
author: Ramón Chancay
date: 2026-09-13
lang: es
tags: [WidgetKit, Live Activities, App Intents, StoreKit 2, Foundation Models, watchOS, iOS]
canonical: https://www.ramonchancay.me/es/blog/lo-que-solo-puedes-hacer-en-nativo-ios
---

# Lo que solo puedes hacer en nativo: WidgetKit, Live Activities, App Intents, StoreKit 2, Foundation Models y watchOS

El [primer post](/es/blog/por-que-un-senior-de-react-native-deberia-aprender-nativo) de esta serie decía que nativo gana donde React Native no llega: fuera del proceso de la app, en el borde del sistema, y el día que una API sale. Doce posts después, este cierra con el detalle de esas capacidades, una por una, con lo que cada una exige y lo que da: WidgetKit y Live Activities, App Intents y Siri, StoreKit 2, el modelo de lenguaje en el dispositivo con Foundation Models, y watchOS. No es una lista de funcionalidades; es el argumento que le presentas a un cliente cuando la pregunta es "¿por qué Swift y no React Native para este producto?", y la respuesta tiene que ser concreta.

<details class="rc-tldr">
<summary class="rc-tldr-btn"><span class="rc-tldr-dot"></span> Resumen para perezosos <svg class="rc-tldr-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></summary>
<div class="rc-tldr-body">
<ul>
<li>Widgets, Live Activities, App Intents y complications de watchOS corren fuera de tu proceso, renderizados por el sistema desde SwiftUI. No hay runtime de JavaScript ahí; la parte de la app que el usuario ve sin abrirla solo se escribe en Swift.</li>
<li>StoreKit 2 y Foundation Models son APIs de Swift con <code>async/await</code> que ofrecen, sin servidor, lo que antes requería uno: verificación de compras en el dispositivo y un modelo de lenguaje local con salida estructurada.</li>
<li>El argumento de venta no es "nativo es mejor": es una lista de superficies del sistema donde el producto puede vivir, con el costo de cada una. El cliente elige cuáles valen el precio; tú tienes que poder construirlas.</li>
</ul>
</div>
</details>

**En este artículo:**

- **Fuera del proceso** — [WidgetKit](#widgetkit-la-app-en-la-pantalla-de-inicio) · [Live Activities](#live-activities-y-la-dynamic-island) · [App Intents y Siri](#app-intents-la-app-que-el-sistema-puede-invocar)
- **Sin servidor** — [StoreKit 2](#storekit-2-compras-verificadas-en-el-dispositivo) · [Foundation Models](#foundation-models-un-modelo-de-lenguaje-en-el-dispositivo)
- **Otras superficies** — [watchOS](#watchos-la-app-en-la-muñeca) · [El argumento para el cliente](#el-argumento-para-el-cliente) · [Cierre de la serie](#cierre-de-la-serie)

## WidgetKit: la app en la pantalla de inicio

Un widget de iOS no es una vista de tu app que el sistema muestra en la pantalla de inicio. Es una **extensión** separada, con su propio target y bundle id, que el sistema ejecuta por su cuenta para pedirle una **línea de tiempo**: una lista de entradas con fecha, cada una con los datos que el widget debe mostrar en ese momento. El sistema renderiza cada entrada con la vista de SwiftUI que le diste, la guarda como imagen, y la muestra cuando toca, sin ejecutar tu código. Por eso no hay animaciones libres ni scroll en un widget, y por eso no puede haber JavaScript: no hay proceso corriendo cuando el widget está en pantalla.

```swift
import WidgetKit
import SwiftUI

struct NextOrderEntry: TimelineEntry {
    let date: Date
    let order: OrderSummary?
}

struct NextOrderProvider: TimelineProvider {
    func placeholder(in context: Context) -> NextOrderEntry {
        NextOrderEntry(date: .now, order: .placeholder)
    }

    func getSnapshot(in context: Context, completion: @escaping (NextOrderEntry) -> Void) {
        completion(NextOrderEntry(date: .now, order: SharedStore.nextOrder()))
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<NextOrderEntry>) -> Void) {
        // Una entrada ahora; el sistema volverá a pedir en ~15 minutos o cuando la app lo pida.
        let entry = NextOrderEntry(date: .now, order: SharedStore.nextOrder())
        completion(Timeline(entries: [entry], policy: .after(.now.addingTimeInterval(15 * 60))))
    }
}

struct NextOrderWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: "NextOrder", provider: NextOrderProvider()) { entry in
            NextOrderView(entry: entry)
                .containerBackground(.fill.tertiary, for: .widget)
        }
        .configurationDisplayName("Próximo pedido")
        .description("Tu siguiente entrega, de un vistazo.")
        .supportedFamilies([.systemSmall, .systemMedium, .accessoryRectangular])
    }
}
```

Lo que hay que resolver alrededor:

- **Compartir datos con la app.** El widget no puede leer la base de datos de la app directamente: son procesos distintos con contenedores distintos. Un **App Group** (entitlement en los dos targets) da un contenedor compartido donde la app escribe lo que el widget necesita (un archivo JSON, un `UserDefaults(suiteName:)`, o la base de datos SQLite entera si ambos usan GRDB con el mismo archivo).
- **Refrescar cuando cambian los datos.** La app llama a `WidgetCenter.shared.reloadTimelines(ofKind:)` después de escribir. El sistema aplica un presupuesto de recargas por día; si lo excedes, ignora las llamadas.
- **Interactividad.** Desde iOS 17, un `Button` o `Toggle` en un widget puede ejecutar un App Intent (siguiente sección), que corre en tu app en segundo plano y devuelve control al widget. Es lo que permite marcar una tarea como hecha sin abrir la app.
- **Familias.** Pantalla de inicio en tres tamaños, pantalla de bloqueo (`accessory*`), StandBy, y la misma extensión sirve para las complications de watchOS.

En React Native, un config plugin de la comunidad puede agregar el target del widget al proyecto, y el puente de datos por App Group funciona igual. Pero `NextOrderView`, el provider y la línea de tiempo se escriben en Swift, sin excepción.

## Live Activities y la Dynamic Island

Una Live Activity es un widget con estado en vivo: aparece en la pantalla de bloqueo y en la Dynamic Island, muestra el progreso de algo que está pasando (un pedido en camino, un partido, un temporizador, un viaje), y se actualiza desde la app o desde el servidor por push, durante hasta ocho horas.

Se construye con **ActivityKit** (para iniciarla y actualizarla) y **WidgetKit** (para la vista):

```swift
import ActivityKit

struct DeliveryAttributes: ActivityAttributes {
    struct ContentState: Codable, Hashable {
        var status: DeliveryStatus
        var etaMinutes: Int
    }
    let orderId: String
    let restaurant: String
}

// Desde la app, al confirmar el pedido:
let activity = try Activity.request(
    attributes: DeliveryAttributes(orderId: order.id, restaurant: order.restaurant),
    content: .init(state: .init(status: .preparing, etaMinutes: 25), staleDate: nil),
    pushType: .token          // el servidor actualizará por push
)

// El token va al backend, que envía actualizaciones a APNs sin que la app esté abierta.
for await tokenData in activity.pushTokenUpdates {
    await api.registerLiveActivityToken(tokenData, orderId: order.id)
}
```

La vista, en la extensión de widgets, define cuatro presentaciones: pantalla de bloqueo, y en la Dynamic Island las variantes compacta, mínima y expandida. Todas en SwiftUI, todas renderizadas por el sistema.

Lo que la hace valiosa para un producto es la combinación de dos cosas que React Native no puede dar: la superficie (la isla y la pantalla de bloqueo son la ubicación más visible del teléfono) y las **actualizaciones por push sin abrir la app**, con un tipo de notificación específico (`liveactivity`) que el backend envía a APNs con el token de la actividad. Es la funcionalidad que más pide un cliente de delivery, transporte o eventos cuando la ve en otra app.

## App Intents: la app que el sistema puede invocar

Un App Intent es una acción de tu app declarada de forma que el sistema pueda invocarla: desde Siri, desde Atajos, desde un widget interactivo, desde Spotlight, desde el botón de acción del iPhone, y desde Apple Intelligence. Se define como un `struct` con parámetros tipados y un método `perform`:

```swift
import AppIntents

struct ReorderLastIntent: AppIntent {
    static let title: LocalizedStringResource = "Repetir último pedido"
    static let description = IntentDescription("Vuelve a pedir lo mismo que la última vez.")

    @Parameter(title: "Restaurante")
    var restaurant: RestaurantEntity?

    static var parameterSummary: some ParameterSummary {
        Summary("Repetir el último pedido de \(\.$restaurant)")
    }

    @Dependency
    private var orders: OrderService

    func perform() async throws -> some IntentResult & ProvidesDialog {
        let order = try await orders.reorderLast(at: restaurant?.id)
        return .result(dialog: "Listo, pedí \(order.summary) de nuevo.")
    }
}

// Frases para Siri, sin entrenar nada:
struct AppShortcuts: AppShortcutsProvider {
    static var appShortcuts: [AppShortcut] {
        AppShortcut(
            intent: ReorderLastIntent(),
            phrases: ["Repite mi último pedido en \(.applicationName)", "Pide lo de siempre en \(.applicationName)"],
            shortTitle: "Repetir pedido",
            systemImageName: "arrow.clockwise"
        )
    }
}
```

Lo que cambia respecto a cualquier integración con Siri anterior: no hay extensión aparte ni archivo de definición; el intent es código Swift que el compilador indexa, y las frases funcionan desde la instalación, sin que el usuario configure nada. Los parámetros pueden ser entidades propias (`RestaurantEntity`) con búsqueda, y el sistema resuelve la desambiguación ("¿cuál restaurante?") por ti.

Desde iOS 18, los App Intents son también la forma en que **Apple Intelligence** conoce lo que tu app puede hacer: los intents anotados con dominios del sistema (`@AssistantIntent`) se vuelven acciones que Siri puede encadenar con contexto de pantalla y de otras apps. Es la capa donde una app deja de ser una pantalla que el usuario abre y pasa a ser un conjunto de acciones que el sistema puede componer, y no existe un camino desde JavaScript hacia ella.

## StoreKit 2: compras verificadas en el dispositivo

La primera versión de StoreKit obligaba a validar recibos en un servidor propio contra el de Apple, con un formato binario que nadie quería parsear, y de ahí nacieron RevenueCat y `react-native-iap`. **StoreKit 2** (desde iOS 15, hoy la única API que Apple recomienda) es `async/await` con verificación integrada:

```swift
import StoreKit

// Productos, con precio localizado.
let products = try await Product.products(for: ["com.empresa.app.pro.monthly", "com.empresa.app.pro.yearly"])

// Compra. El resultado viene firmado; `verified` significa que Apple lo firmó.
switch try await product.purchase() {
case .success(let verification):
    let transaction = try verification.payloadValue   // lanza si la firma no es válida
    await transaction.finish()
    await grantAccess(for: transaction.productID)
case .userCancelled, .pending:
    break
@unknown default:
    break
}

// Estado actual de la suscripción, en cualquier arranque, sin servidor.
for await result in Transaction.currentEntitlements {
    if case .verified(let transaction) = result {
        await grantAccess(for: transaction.productID)
    }
}

// Cambios en segundo plano: renovaciones, reembolsos, compras en otro dispositivo.
for await result in Transaction.updates {
    if case .verified(let transaction) = result {
        await transaction.finish()
        await grantAccess(for: transaction.productID)
    }
}
```

Sobre eso, `SubscriptionStoreView` y `StoreView` son vistas de SwiftUI que dibujan la pantalla de suscripción completa (planes, precios, prueba gratuita, términos) con el diseño del sistema, en una línea, y App Store Connect ofrece notificaciones de servidor (App Store Server Notifications v2) para que el backend se entere de renovaciones y cancelaciones sin consultar.

RevenueCat sigue teniendo un lugar cuando hay Android, web, o necesitas su panel de métricas y experimentos. Pero para una app solo iOS, StoreKit 2 más las notificaciones de servidor cubren el flujo completo con código propio y sin cuota. Es una de las conversaciones que cambian con un cliente: "las suscripciones no requieren un servicio externo" se traduce en costo mensual.

## Foundation Models: un modelo de lenguaje en el dispositivo

Desde iOS 26, el framework **Foundation Models** expone el modelo de lenguaje que Apple ejecuta en el dispositivo para Apple Intelligence: unos tres mil millones de parámetros, sin red, sin costo por token, sin que los datos del usuario salgan del teléfono. Se usa desde Swift con una sesión y, lo que lo hace útil para producto, con **salida estructurada** hacia tus propios tipos:

```swift
import FoundationModels

@Generable
struct OrderSuggestion {
    @Guide(description: "Nombre corto del plato sugerido")
    let dish: String
    @Guide(description: "Motivo en una frase, en el idioma del usuario")
    let reason: String
    @Guide(.range(1...5))
    let confidence: Int
}

let session = LanguageModelSession(
    instructions: "Sugieres un plato basándote en los pedidos anteriores del usuario. Responde en español."
)

let suggestion = try await session.respond(
    to: "Pedidos anteriores: \(history.joined(separator: ", ")). Es viernes por la noche.",
    generating: OrderSuggestion.self
).content

print(suggestion.dish, suggestion.reason, suggestion.confidence)
```

`@Generable` genera el esquema desde el `struct`, y el modelo produce una instancia válida de ese tipo, no un texto que hay que parsear. Hay **tool calling** (el modelo puede invocar funciones tuyas para obtener datos), streaming de la salida parcial, y un modo de disponibilidad que hay que comprobar (`SystemLanguageModel.default.availability`), porque el modelo requiere dispositivos compatibles con Apple Intelligence y que el usuario lo tenga activado.

Lo que cabe en un modelo de ese tamaño y lo que no: resúmenes, clasificación, extracción de campos, sugerencias sobre datos del usuario, reescritura, etiquetado. No cabe razonamiento largo ni conocimiento del mundo amplio; para eso sigue el modelo en la nube, con el mismo patrón de [routing](/es/blog/routing-modelos-openrouter-deepseek) que uso en el backend, con el modelo local como primer nivel.

Para un cliente, la frase que importa es: **funcionalidades de IA sobre los datos del usuario, sin enviar esos datos a ningún servidor y sin costo de inferencia**. Con React Native se puede llegar a esta API escribiendo un módulo nativo, que es exactamente escribir el código de arriba en Swift más el puente; el argumento del primer post, otra vez.

## watchOS: la app en la muñeca

Una app de Apple Watch es un target más del mismo proyecto, en SwiftUI, con los mismos packages de dominio y red que la app del iPhone. Lo que agrega y lo que restringe:

- **Complications** (las pequeñas vistas en las esferas del reloj) son widgets de WidgetKit con familias `accessory*`. El mismo código del widget de iPhone, con otras familias.
- **Comunicación con el iPhone** por `WatchConnectivity`: mensajes, contexto de aplicación y transferencia de archivos, con la app del reloj capaz de funcionar sola si tiene red.
- **HealthKit y sensores** con acceso directo: frecuencia cardíaca, entrenamientos, movimiento. Es el motivo de la mayoría de las apps de reloj que valen la pena.
- **Restricciones reales**: tiempo de ejecución en segundo plano muy limitado, pantalla pequeña, sin teclado completo, y una interacción que debe resolverse en segundos.

No existe React Native para watchOS. Si el producto tiene un reloj en la hoja de ruta, la app del reloj es Swift desde el primer día, y compartir dominio con una app de iPhone en React Native significa mantener dos modelos de datos. Es uno de los casos donde la decisión de plataforma se toma por el producto entero, no por la app principal.

## El argumento para el cliente

Cuando un cliente pregunta por qué Swift, la respuesta que funciona no es una comparación técnica sino una lista de **superficies donde su producto puede existir** y que con React Native no están, o llegan tarde. La forma en que la presento:

| Superficie | Qué ve el usuario | Qué exige | Cuándo vale el precio |
|---|---|---|---|
| Widget | Datos del producto sin abrir la app | Target de extensión, App Group, línea de tiempo | Productos con un dato que cambia y que el usuario quiere ver seguido (pedidos, saldos, progreso) |
| Live Activity | Progreso en vivo en la pantalla de bloqueo y la Dynamic Island | ActivityKit, push por servidor | Delivery, transporte, eventos, cualquier cosa con un "en curso" |
| App Intents | Siri, Atajos, widgets interactivos, Apple Intelligence | Un `struct` por acción | Productos con acciones repetidas que el usuario quiere disparar sin abrir la app |
| StoreKit 2 | Suscripciones con la pantalla del sistema, sin servicio externo | Código propio, notificaciones de servidor | Cualquier app con suscripción solo en iOS |
| Foundation Models | IA sobre los datos del usuario, sin red ni costo | Dispositivos compatibles, diseño de prompts | Productos con datos privados y funciones de resumen, clasificación o sugerencia |
| watchOS | La app en la muñeca, con sensores | Target propio, dominio compartido | Salud, deporte, notificaciones críticas, control rápido |
| APIs del día uno | Lo nuevo de cada iOS, en septiembre | Recompilar | Productos que compiten en percepción de calidad con las apps de Apple |

La conversación termina con la [tabla del primer post](/es/blog/por-que-un-senior-de-react-native-deberia-aprender-nativo): si el producto necesita dos plataformas con un equipo y correcciones diarias por OTA, y ninguna de las superficies de arriba está en la hoja de ruta, React Native es la respuesta correcta y hay que decirlo. Si dos o más de esas superficies son el producto, Swift. Y si es una app React Native con un widget y una Live Activity, la respuesta es híbrida, y quien la construye tiene que saber las dos cosas.

> El cambio en las conversaciones con clientes no vino de saber más Swift. Vino de poder poner la tabla de arriba delante y decir cuánto cuesta cada fila. Un cliente que entiende que una Live Activity son dos semanas y un widget una, decide con información; uno al que le dicen "eso no se puede" busca a otro.

## Cierre de la serie

Trece posts, en el orden en que un senior de React Native encuentra los problemas al construir su primera app seria en Swift:

1. La [tesis](/es/blog/por-que-un-senior-de-react-native-deberia-aprender-nativo): cuándo nativo gana, cuándo pierde, y lo que vas a extrañar.
2. [Swift para quien domina TypeScript](/es/blog/swift-para-quien-domina-typescript): valor frente a referencia, opcionales, enums, protocolos, Codable.
3. [SwiftUI no es React](/es/blog/swiftui-no-es-react): body, identidad, modifiers, layout, property wrappers.
4. [Estado y arquitectura](/es/blog/estado-y-arquitectura-de-zustand-a-observable-y-tca): @Observable, MVVM, TCA, inyección, packages.
5. [Navegación](/es/blog/navegacion-en-swiftui-sin-file-based-routing): NavigationStack, enum de rutas, deep links.
6. [Data](/es/blog/data-en-swift-lo-que-tanstack-query-hacia-por-ti): URLSession, repositorio con AsyncStream, la base de datos como caché.
7. [Concurrencia](/es/blog/concurrencia-en-swift-adios-al-event-loop): sin event loop, actores, Sendable, Swift 6.
8. [Persistencia](/es/blog/persistencia-en-ios-swiftdata-grdb-keychain): SwiftData, GRDB, Keychain, offline-first, CloudKit.
9. [Del ecosistema expo-* a los frameworks de Apple](/es/blog/del-ecosistema-expo-a-los-frameworks-de-apple): la tabla y los permisos.
10. [UI, theming y animaciones](/es/blog/ui-theming-y-animaciones-en-swiftui-sin-nativewind-ni-reanimated): tokens, accesibilidad, animaciones, Liquid Glass.
11. [Build, firma y App Store](/es/blog/build-firma-y-app-store-lo-que-eas-te-escondia): certificados, match, TestFlight, App Review, Tuist.
12. [Testing y tooling](/es/blog/testing-y-tooling-en-ios-para-seniors): Swift Testing, snapshots, mocks, Instruments, compilación.
13. Este: lo que solo puedes hacer en nativo.

Si tuviera que resumir la serie en una idea, sería la del primer post: para un senior, aprender nativo no reemplaza a React Native, lo completa. Lo que cambia no es qué herramienta usas, sino que la elección pasa a ser tuya, por proyecto, con criterio, y con la capacidad de construir lo que la elección implique. Lo que se pierde es real (Fast Refresh, OTA, npm, una base de código) y lo que se gana también (widgets, Live Activities, intents, el modelo local, el día uno de cada API).

## Preguntas frecuentes

### ¿Puedo agregar un widget a una app React Native existente sin reescribirla?

Sí. Un target de extensión de widget en el proyecto de iOS, un App Group para compartir datos, y un módulo (o un config plugin de Expo) para que la app escriba en el contenedor compartido y llame a `reloadTimelines`. El widget en sí es Swift. Es el punto de entrada más frecuente al nativo para equipos de React Native, y una buena primera app de Swift.

### ¿Foundation Models funciona en todos los dispositivos?

No. Requiere un dispositivo compatible con Apple Intelligence (iPhone 15 Pro o posterior, y los iPad y Mac con chips recientes), iOS 26 o superior, y que el usuario tenga Apple Intelligence activado. La app debe comprobar `availability` y ofrecer una alternativa (un modelo en la nube o la función desactivada) cuando no está disponible.

### ¿Las Live Activities necesitan un servidor?

Para actualizaciones mientras la app no está abierta, sí: el backend envía pushes a APNs con el token de la actividad. Sin servidor, la app puede actualizar la actividad mientras está en primer plano o en el tiempo de segundo plano que el sistema le concede, que es corto. Un temporizador o una cuenta regresiva funcionan sin servidor porque la vista puede mostrar tiempo relativo por sí sola.

### ¿StoreKit 2 sirve si también tengo Android?

Sirve para iOS, y en Android su equivalente es Google Play Billing. Lo que se pierde sin RevenueCat es el modelo unificado de suscriptores entre plataformas y el panel. Si el backend ya recibe las notificaciones de servidor de las dos tiendas, se puede unificar ahí; si no, RevenueCat sigue siendo la opción práctica para dos plataformas.

### ¿Hay algo en Android equivalente a todo esto?

Widgets (Glance con Compose), Live Updates desde Android 16 con un alcance más limitado que las Live Activities, App Actions para el Asistente, Play Billing, y ML Kit o Gemini Nano en el dispositivo. La correspondencia no es uno a uno y el modelo local de Google está en menos dispositivos. Wear OS existe y se programa en Kotlin. La conclusión de la serie aplica igual: para esas superficies, el código es nativo de cada plataforma.

## Conclusión

Lo que solo puedes hacer en nativo comparte una característica: vive fuera del proceso de tu app o en una API que sale primero en Swift. Widgets y Live Activities los renderiza el sistema desde una línea de tiempo en SwiftUI. App Intents convierten la app en acciones que Siri, Atajos y Apple Intelligence pueden invocar. StoreKit 2 verifica compras en el dispositivo sin servicio externo. Foundation Models corre un modelo de lenguaje con salida estructurada sin red ni costo. watchOS es otro target del mismo proyecto. Y cada septiembre, lo nuevo llega el día uno.

Para empezar: elige la superficie que más vale para tu producto (casi siempre un widget), escribe la extensión con un App Group y una línea de tiempo, y ponla delante del cliente antes de escribir la segunda. Con esa demo, la tabla de este post deja de ser teoría y pasa a ser una lista de precios, que es lo que un cliente necesita para decidir, y lo que un senior de React Native que ahora también sabe Swift puede ofrecer.
