# App Intents y Siri Shortcuts: automatización avanzada de iOS 2026 > Guía completa de App Intents y Siri Shortcuts para iOS 18+. Crear acciones personalizadas para Siri, integrar Apple Intelligence y automatizar la app Swift en 2026. - Published: 2026-03-08 - Updated: 2026-04-29 - Author: SharpSkill - Tags: app-intents, siri-shortcuts, ios, swift, apple-intelligence - Reading time: 15 min --- En 2026, con Apple Intelligence y el framework App Intents, las aplicaciones iOS entran en una era donde la intención del usuario importa más que la interfaz gráfica. Las apps que no exponen intents se vuelven invisibles en un sistema operativo orientado a la IA. App Intents constituye la base que permite a Siri, Spotlight, los widgets y el Action Button interactuar con las funcionalidades de la aplicación. > **Lo que cubre este artículo** > > Este artículo presenta la creación completa de App Intents y Siri Shortcuts para iOS 18+, desde los conceptos fundamentales hasta la integración con Apple Intelligence y los App Intent Domains. ## Comprender el framework App Intents El framework App Intents, introducido con iOS 16, moderniza la construcción de intents en Swift al reemplazar el antiguo SiriKit Intents framework. Esta arquitectura declarativa permite crear acciones detectables por el sistema: Spotlight, app Shortcuts, Siri y el Action Button. ```swift // TaskIntent.swift import AppIntents // Un AppIntent representa una acción que el usuario puede ejecutar struct CreateTaskIntent: AppIntent { // Título mostrado en Shortcuts y Siri static var title: LocalizedStringResource = "Crear una tarea" // Descripción para accesibilidad y sugerencias static var description = IntentDescription( "Crea una nueva tarea en la aplicación." ) // Parámetro con validación automática @Parameter(title: "Título de la tarea") var taskTitle: String // Parámetro opcional con valor por defecto @Parameter(title: "Prioridad", default: .medium) var priority: TaskPriority // Ejecución de la acción func perform() async throws -> some IntentResult & ReturnsValue { // Crear la tarea mediante el servicio let task = TaskService.shared.createTask( title: taskTitle, priority: priority ) // Retornar la entidad creada para encadenar return .result(value: TaskEntity(task: task)) } } ``` El intent declara sus parámetros mediante el property wrapper `@Parameter`, permitiendo a Siri solicitar los valores faltantes. El método `perform()` ejecuta la lógica de negocio y retorna un resultado tipado. ## Definir App Entities para los datos Las App Entities representan los "sustantivos" de la aplicación: los objetos sobre los que operan los intents. Permiten a Siri comprender y manipular los datos de la app. ```swift // TaskEntity.swift import AppIntents // Modelo de datos interno struct Task: Identifiable, Codable { let id: UUID var title: String var priority: TaskPriority var isCompleted: Bool var dueDate: Date? } // Entidad expuesta al sistema struct TaskEntity: AppEntity { // Identificador único requerido var id: UUID // Propiedades visibles var title: String var priority: TaskPriority var isCompleted: Bool // Configuración de visualización en el sistema static var typeDisplayRepresentation: TypeDisplayRepresentation = "Tarea" // Representación visual de la instancia var displayRepresentation: DisplayRepresentation { DisplayRepresentation( title: "\(title)", subtitle: "\(priority.rawValue)", image: .init(systemName: isCompleted ? "checkmark.circle.fill" : "circle") ) } // Query por defecto para buscar entidades static var defaultQuery = TaskEntityQuery() // Inicializador desde el modelo interno init(task: Task) { self.id = task.id self.title = task.title self.priority = task.priority self.isCompleted = task.isCompleted } } // Query para buscar y filtrar entidades struct TaskEntityQuery: EntityQuery { // Búsqueda por identificadores func entities(for identifiers: [UUID]) async throws -> [TaskEntity] { TaskService.shared.fetchTasks() .filter { identifiers.contains($0.id) } .map { TaskEntity(task: $0) } } // Sugerencias mostradas en la interfaz func suggestedEntities() async throws -> [TaskEntity] { TaskService.shared.fetchTasks() .filter { !$0.isCompleted } .prefix(5) .map { TaskEntity(task: $0) } } } ``` La `EntityQuery` define cómo el sistema busca y sugiere entidades. Los métodos `entities(for:)` y `suggestedEntities()` alimentan las interfaces de Siri y Shortcuts. > **App Enums vs App Entities** > > Usar `AppEnum` para tipos con un conjunto fijo de valores (prioridad, estado), y `AppEntity` para tipos dinámicos creados por el usuario (tareas, notas, contactos). ## Crear App Enums para valores fijos Los App Enums exponen tipos enumerados al sistema, permitiendo a Siri ofrecer opciones contextuales. ```swift // TaskPriority.swift import AppIntents // Enum expuesto al sistema enum TaskPriority: String, AppEnum, Codable { case low case medium case high // Nombre del tipo mostrado static var typeDisplayRepresentation: TypeDisplayRepresentation = "Prioridad" // Representación de cada caso static var caseDisplayRepresentations: [TaskPriority: DisplayRepresentation] = [ .low: DisplayRepresentation( title: "Baja", image: .init(systemName: "arrow.down.circle") ), .medium: DisplayRepresentation( title: "Media", image: .init(systemName: "minus.circle") ), .high: DisplayRepresentation( title: "Alta", image: .init(systemName: "exclamationmark.circle") ) ] } // Enum para el estado de la tarea enum TaskStatus: String, AppEnum, Codable { case pending case inProgress case completed static var typeDisplayRepresentation: TypeDisplayRepresentation = "Estado" static var caseDisplayRepresentations: [TaskStatus: DisplayRepresentation] = [ .pending: "Pendiente", .inProgress: "En progreso", .completed: "Completada" ] } ``` Las representaciones visuales (iconos SF Symbols) enriquecen la visualización en Shortcuts y en las sugerencias de Siri. ## Implementar AppShortcutsProvider El `AppShortcutsProvider` expone los App Shortcuts al sistema, dejándolos disponibles inmediatamente sin configuración del usuario. Estos atajos aparecen en Spotlight, Siri y el Action Button. ```swift // ShortcutsProvider.swift import AppIntents // Provider que declara todos los shortcuts de la app struct TaskAppShortcutsProvider: AppShortcutsProvider { // Máximo 10 shortcuts por aplicación @AppShortcutsBuilder static var appShortcuts: [AppShortcut] { // Shortcut para crear una tarea AppShortcut( intent: CreateTaskIntent(), phrases: [ // El placeholder .applicationName es OBLIGATORIO "Crear una tarea con \(.applicationName)", "Nueva tarea en \(.applicationName)", "Añadir una tarea a \(.applicationName)" ], shortTitle: "Crear tarea", systemImageName: "plus.circle" ) // Shortcut para listar tareas AppShortcut( intent: ListTasksIntent(), phrases: [ "Mostrar mis tareas en \(.applicationName)", "Ver tareas de \(.applicationName)", "Cuáles son mis tareas \(.applicationName)" ], shortTitle: "Mis tareas", systemImageName: "list.bullet" ) // Shortcut con parámetro dinámico AppShortcut( intent: CompleteTaskIntent(), phrases: [ "Completar \(\.$taskName) en \(.applicationName)", "Marcar \(\.$taskName) como hecha con \(.applicationName)" ], shortTitle: "Completar tarea", systemImageName: "checkmark.circle" ) } } ``` Las frases de voz deben incluir el placeholder `\(.applicationName)` para que Siri identifique la app destino. Los parámetros dinámicos como `\(\.$taskName)` permiten comandos contextuales. > **Límite de 10 shortcuts** > > Una aplicación puede declarar como máximo 10 App Shortcuts. Conviene priorizar las acciones más frecuentes y útiles para los usuarios. ## Intents con parámetros complejos Los intents pueden aceptar parámetros complejos, incluyendo entidades y opciones de configuración. ```swift // CompleteTaskIntent.swift import AppIntents struct CompleteTaskIntent: AppIntent { static var title: LocalizedStringResource = "Completar una tarea" static var description = IntentDescription( "Marca una tarea como completada." ) // Parámetro tipo entidad con búsqueda automática @Parameter(title: "Tarea") var task: TaskEntity // Parámetro opcional con fecha @Parameter(title: "Fecha de finalización") var completionDate: Date? // Configuración del diálogo Siri static var parameterSummary: some ParameterSummary { Summary("Completar \(\.$task)") { \.$completionDate } } func perform() async throws -> some IntentResult & ProvidesDialog { // Actualiza la tarea TaskService.shared.completeTask( id: task.id, completionDate: completionDate ?? Date() ) // Feedback de voz para Siri return .result( dialog: "La tarea \(task.title) ha sido marcada como completada." ) } } // Intent que retorna una lista de entidades struct ListTasksIntent: AppIntent { static var title: LocalizedStringResource = "Listar tareas" // Filtro opcional @Parameter(title: "Estado", default: nil) var statusFilter: TaskStatus? @Parameter(title: "Prioridad", default: nil) var priorityFilter: TaskPriority? func perform() async throws -> some IntentResult & ReturnsValue<[TaskEntity]> { var tasks = TaskService.shared.fetchTasks() // Aplicar filtros if let status = statusFilter { tasks = tasks.filter { switch status { case .completed: return $0.isCompleted case .pending, .inProgress: return !$0.isCompleted } } } if let priority = priorityFilter { tasks = tasks.filter { $0.priority == priority } } let entities = tasks.map { TaskEntity(task: $0) } return .result(value: entities) } } ``` El `ParameterSummary` define cómo Siri presenta el intent durante la ejecución por voz, ofreciendo un feedback natural. ## Integración con Apple Intelligence iOS 18 introduce los App Intent Domains, colecciones de APIs diseñadas para funcionalidades específicas. Estos dominios permiten a Apple Intelligence comprender y ejecutar acciones con mayor precisión. ```swift // BookmarkDomainIntents.swift import AppIntents // Conformidad con el dominio Bookmarks para integración Apple Intelligence struct SaveBookmarkIntent: AppIntent { static var title: LocalizedStringResource = "Guardar un marcador" // Parámetro URL con validación automática @Parameter(title: "URL") var url: URL @Parameter(title: "Título", default: nil) var title: String? @Parameter(title: "Carpeta", default: nil) var folder: BookmarkFolderEntity? // Abrir la app si es necesario static var openAppWhenRun: Bool = false func perform() async throws -> some IntentResult & ProvidesDialog { let bookmark = BookmarkService.shared.save( url: url, title: title, folder: folder?.id ) return .result( dialog: "Marcador guardado: \(bookmark.title)" ) } } // Intent con conciencia del contenido en pantalla (iOS 18.4+) struct AnalyzeScreenContentIntent: AppIntent { static var title: LocalizedStringResource = "Analizar contenido" // Acceso al contexto de pantalla vía Apple Intelligence @Parameter(title: "Contexto") var screenContext: String? func perform() async throws -> some IntentResult & ProvidesDialog { guard let context = screenContext else { return .result(dialog: "No hay contenido que analizar.") } // Procesar contenido extraído por Apple Intelligence let analysis = ContentAnalyzer.analyze(context) return .result(dialog: analysis.summary) } } ``` Los dominios predefinidos (Books, Camera, Spreadsheets) permiten a Siri responder a las solicitudes con precisión gracias a modelos entrenados en estas tareas específicas. ## Acciones Siri con confirmación del usuario Para acciones sensibles, el sistema puede solicitar confirmación antes de la ejecución. ```swift // DeleteTaskIntent.swift import AppIntents struct DeleteTaskIntent: AppIntent { static var title: LocalizedStringResource = "Eliminar una tarea" @Parameter(title: "Tarea") var task: TaskEntity // Requiere confirmación del usuario static var isDiscoverable: Bool = true func perform() async throws -> some IntentResult & ProvidesDialog { // Solicitar confirmación mediante diálogo try await requestConfirmation( result: .result( dialog: "¿Confirmas que quieres eliminar \(task.title)?" ) ) // Eliminar tras confirmación TaskService.shared.deleteTask(id: task.id) return .result( dialog: "La tarea \(task.title) ha sido eliminada." ) } } // Intent con varias etapas de diálogo struct ScheduleTaskIntent: AppIntent { static var title: LocalizedStringResource = "Programar una tarea" @Parameter(title: "Tarea") var task: TaskEntity @Parameter(title: "Fecha") var scheduledDate: Date @Parameter(title: "Recordatorio", default: true) var setReminder: Bool static var parameterSummary: some ParameterSummary { When(\.$setReminder, .equalTo, true) { Summary("Programar \(\.$task) para \(\.$scheduledDate) con recordatorio") } otherwise: { Summary("Programar \(\.$task) para \(\.$scheduledDate)") } } func perform() async throws -> some IntentResult & ProvidesDialog { TaskService.shared.schedule( taskId: task.id, date: scheduledDate, reminder: setReminder ) let dateFormatter = DateFormatter() dateFormatter.dateStyle = .medium dateFormatter.timeStyle = .short let formattedDate = dateFormatter.string(from: scheduledDate) return .result( dialog: "Tarea programada para el \(formattedDate)." ) } } ``` El método `requestConfirmation` pausa la ejecución hasta la validación del usuario, protegiendo contra acciones accidentales. ## Intents en widgets interactivos Los App Intents se integran de forma natural con WidgetKit para crear widgets interactivos en iOS 17+. ```swift // TaskWidgetIntents.swift import AppIntents import WidgetKit // Intent optimizado para widget (ejecución rápida) struct ToggleTaskFromWidgetIntent: AppIntent { static var title: LocalizedStringResource = "Alternar tarea" @Parameter(title: "ID de la tarea") var taskID: String init() {} init(taskID: UUID) { self.taskID = taskID.uuidString } // Sin diálogo para los widgets func perform() async throws -> some IntentResult { guard let uuid = UUID(uuidString: taskID) else { return .result() } TaskService.shared.toggleCompletion(taskId: uuid) // Recarga inmediata del widget WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget") return .result() } } // Vista del widget con botón interactivo import SwiftUI struct TaskWidgetView: View { let task: Task var body: some View { Button(intent: ToggleTaskFromWidgetIntent(taskID: task.id)) { HStack { Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle") .foregroundStyle(task.isCompleted ? .green : .secondary) Text(task.title) .strikethrough(task.isCompleted) } .padding() } .buttonStyle(.plain) } } ``` Los widgets utilizan la sintaxis `Button(intent:)` para conectar la interacción directamente con el App Intent, sin abrir la aplicación. ## Configuración del Action Button El Action Button del iPhone 15 Pro y posteriores puede disparar App Shortcuts directamente. ```swift // ActionButtonIntent.swift import AppIntents // Intent optimizado para Action Button struct QuickCaptureIntent: AppIntent { static var title: LocalizedStringResource = "Captura rápida" static var description = IntentDescription( "Crea rápidamente una tarea con un título." ) // Abre la app para entrada static var openAppWhenRun: Bool = true func perform() async throws -> some IntentResult & OpensIntent { // Notificación para abrir la pantalla de captura rápida NotificationCenter.default.post( name: .quickCaptureTriggered, object: nil ) return .result(opensIntent: ShowQuickCaptureViewIntent()) } } // Declarar en AppShortcutsProvider para Action Button extension TaskAppShortcutsProvider { @AppShortcutsBuilder static var appShortcuts: [AppShortcut] { // ... otros shortcuts AppShortcut( intent: QuickCaptureIntent(), phrases: [ "Captura rápida \(.applicationName)", "Nota rápida \(.applicationName)" ], shortTitle: "Capturar", systemImageName: "bolt.circle" ) } } ``` Los usuarios pueden configurar el Action Button para disparar este shortcut desde Ajustes > Botón de acción. > **Macros iOS 18+** > > iOS 18 introduce los macros `@DeferredProperty` y `@ComputedProperty` para reducir el boilerplate. Los App Intents también pueden residir en Swift Packages para reutilización multiplataforma. ## Intents en Swift Packages Los App Intents pueden definirse en Swift Packages para compartirlos entre iOS, macOS y watchOS. ```swift // Package.swift // swift-tools-version: 5.9 import PackageDescription let package = Package( name: "TaskIntents", platforms: [ .iOS(.v17), .macOS(.v14), .watchOS(.v10) ], products: [ .library(name: "TaskIntents", targets: ["TaskIntents"]) ], dependencies: [], targets: [ .target( name: "TaskIntents", dependencies: [] ) ] ) // Sources/TaskIntents/SharedIntents.swift import AppIntents // Intent compartido multiplataforma public struct SharedCreateTaskIntent: AppIntent { public static var title: LocalizedStringResource = "Crear una tarea" @Parameter(title: "Título") public var taskTitle: String public init() {} public func perform() async throws -> some IntentResult { // Lógica compartida await TaskRepository.shared.create(title: taskTitle) return .result() } } // Extensión específica de plataforma en la app #if os(iOS) extension SharedCreateTaskIntent { // Comportamiento específico iOS static var openAppWhenRun: Bool = false } #endif ``` Esta arquitectura mantiene un código único para los intents adaptando el comportamiento por plataforma. ## Probar los App Intents Los App Intents pueden testearse de forma unitaria como cualquier código Swift. ```swift // TaskIntentTests.swift import XCTest import AppIntents @testable import TaskApp final class TaskIntentTests: XCTestCase { override func setUp() { super.setUp() // Resetear el servicio para tests aislados TaskService.shared.reset() } func testCreateTaskIntent() async throws { // Given var intent = CreateTaskIntent() intent.taskTitle = "Tarea de prueba" intent.priority = .high // When let result = try await intent.perform() // Then let tasks = TaskService.shared.fetchTasks() XCTAssertEqual(tasks.count, 1) XCTAssertEqual(tasks.first?.title, "Tarea de prueba") XCTAssertEqual(tasks.first?.priority, .high) } func testCompleteTaskIntent() async throws { // Given let task = TaskService.shared.createTask( title: "Tarea por completar", priority: .medium ) var intent = CompleteTaskIntent() intent.task = TaskEntity(task: task) // When _ = try await intent.perform() // Then let updatedTask = TaskService.shared.fetchTask(id: task.id) XCTAssertTrue(updatedTask?.isCompleted ?? false) } func testEntityQuery() async throws { // Given let task1 = TaskService.shared.createTask(title: "Tarea 1", priority: .low) let task2 = TaskService.shared.createTask(title: "Tarea 2", priority: .high) let query = TaskEntityQuery() // When let entities = try await query.entities(for: [task1.id, task2.id]) // Then XCTAssertEqual(entities.count, 2) } } ``` Los tests verifican el comportamiento de los intents independientemente de la interfaz del sistema. ## Buenas prácticas y optimizaciones Varios patrones aseguran App Intents performantes y una experiencia óptima para el usuario. ```swift // BestPractices.swift import AppIntents // 1. Frases localizadas struct LocalizedTaskIntent: AppIntent { static var title: LocalizedStringResource = "task.create.title" static var description = IntentDescription( "task.create.description", categoryName: "task.category" ) @Parameter(title: "task.parameter.title") var taskTitle: String func perform() async throws -> some IntentResult { // ... return .result() } } // 2. Manejo correcto de errores enum TaskIntentError: Error, CustomLocalizedStringResourceConvertible { case taskNotFound case invalidInput case serviceUnavailable var localizedStringResource: LocalizedStringResource { switch self { case .taskNotFound: return "Tarea no encontrada." case .invalidInput: return "Datos inválidos." case .serviceUnavailable: return "Servicio temporalmente no disponible." } } } struct RobustTaskIntent: AppIntent { static var title: LocalizedStringResource = "Intent robusto" @Parameter(title: "ID") var taskId: String func perform() async throws -> some IntentResult & ProvidesDialog { guard let uuid = UUID(uuidString: taskId) else { throw TaskIntentError.invalidInput } guard let task = TaskService.shared.fetchTask(id: uuid) else { throw TaskIntentError.taskNotFound } return .result(dialog: "Tarea encontrada: \(task.title)") } } // 3. Optimización de EntityQuery struct OptimizedTaskQuery: EntityStringQuery { // Búsqueda textual optimizada func entities(matching string: String) async throws -> [TaskEntity] { // Búsqueda en el servicio con límite TaskService.shared.search(query: string, limit: 10) .map { TaskEntity(task: $0) } } // Sugerencias limitadas para rendimiento func suggestedEntities() async throws -> [TaskEntity] { TaskService.shared.fetchRecentTasks(limit: 5) .map { TaskEntity(task: $0) } } } // 4. Focus Filter para los modos de Concentración struct TaskFocusFilter: SetFocusFilterIntent { static var title: LocalizedStringResource = "Filtrar tareas" @Parameter(title: "Mostrar solo prioridad alta") var showHighPriorityOnly: Bool func perform() async throws -> some IntentResult { TaskService.shared.setFocusFilter(highPriorityOnly: showHighPriorityOnly) return .result() } } ``` Estos patrones aseguran una integración fluida con el sistema preservando un rendimiento óptimo. ## Conclusión Los App Intents y Siri Shortcuts transforman la manera en que los usuarios interactúan con las aplicaciones iOS. En 2026, con Apple Intelligence, exponer intents ya no es opcional sino esencial para ofrecer una experiencia moderna e intuitiva. ### Checklist App Intents iOS 18+ - ✅ Crear AppIntents para las acciones principales de la app - ✅ Definir AppEntities para los datos manipulables - ✅ Usar AppEnum para los tipos enumerados - ✅ Implementar AppShortcutsProvider con frases de voz - ✅ Respetar el límite máximo de 10 App Shortcuts - ✅ Incluir `\(.applicationName)` en todas las frases - ✅ Configurar ParameterSummary para feedback Siri - ✅ Integrar dominios Apple Intelligence si aplica - ✅ Probar los intents con tests unitarios - ✅ Localizar títulos y descripciones --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/es/blog/ios/app-intents-siri-shortcuts-advanced-ios-automation