Saltar al contenido principal

CI/CD para Capacitor: Errores comunes y soluciones

Errores de CI/CD que rompen builds de Capacitor: signing de iOS, runners de macOS, Xcode 26, cap sync desactualizado, version codes, rechazos en stores y sus soluciones.

Problemas con las identidades de firma de Code y fallos en el pipeline de Capacitor

La mayoría de los fallos de CI/CD en Capacitor vienen de la misma lista corta: signing de code para iOS, herramientas de macOS faltantes o desactualizadas, un web build que nunca llegó al proyecto nativo, build numbers reutilizados y requisitos de la store que solo fallan al hacer upload. Cada uno tiene una causa conocida y una solución que puedes aplicar una vez. Esta guía agrupa los errores por etapa del pipeline, con el error que verás, por qué ocurre y qué cambiar.

Si estás configurando un pipeline desde cero, lee Configuración de CI/CD para apps de Capacitor primero, y luego usa esta lista para reforzarlo.

Fase 1: construir la capa web

Error común: la app nativa incluye un build web antiguo

Síntoma: El CI pasa y la app se instala, pero muestra la UI de ayer.

Causa: cap sync copia lo que haya en webDir en ese momento. Si el pipeline se ejecuta cap sync antes del build web, o el build web escribe en una carpeta distinta a webDir en capacitor.config.ts, el proyecto nativo se queda con archivos obsoletos.

Solución: ejecuta siempre los pasos en este orden y falla si la carpeta de salida está vacía.

bun install --frozen-lockfile
bun run build
test -f dist/index.html || { echo "web build missing"; exit 1; }
bunx cap sync

Asegúrate de que webDir coincide con el output de tu bundler (dist si usas Vite, www para Angular con Ionic, build para algunas configuraciones de React).

Error común: dejar la URL del dev server en la config

Síntoma: la build de release muestra una pantalla en blanco o intenta cargar http://192.168.x.x:5173.

Causa: server.url en capacitor.config.ts se configuró para live reload y se hizo commit.

Solución: nunca hagas commit de server.url. Léelo desde una variable de entorno que el CI nunca defina:

import type { CapacitorConfig } from '@capacitor/cli'

const config: CapacitorConfig = {
  appId: 'com.example.app',
  appName: 'Example',
  webDir: 'dist',
  ...(process.env.LIVE_RELOAD_URL && {
    server: { url: process.env.LIVE_RELOAD_URL, cleartext: true },
  }),
}

export default config

Error común: variables de entorno incrustadas en el momento equivocado

Síntoma: la app de producción se conecta al API de staging.

Causa: Vite, webpack y Angular incluyen variables de entorno en línea durante el build. El valor presente cuando bun run build se ejecute es el que quedará en el binario y en cualquier live update generado con ese mismo job.

Solución: define las variables según el entorno antes del build web, por cada job, y genera bundles separados para staging y producción. No intentes cambiarlas después cap sync.

Error común: drift en el lockfile

Síntoma: la versión de un plugin en CI es distinta a la de tu máquina y la compilación nativa falla por símbolos faltantes.

Solución: haz commit del lockfile e instala con bun install --frozen-lockfile (o npm ci). Fija @capacitor/core, @capacitor/ios, @capacitor/android, y @capacitor/cli la misma versión. Las versiones desajustadas causan errores nativos frecuentes; consulta Soluciona errores de desajuste de versión en Capacitor.

Etapa 2: El toolchain nativo

Error común: versión incorrecta de Node, JDK o Xcode

Síntoma: Unsupported class file major version, The engine "node" is incompatible, o errores de Xcode con funciones de SDK.

Causa: cambian las imágenes de los runners alojados, y Capacitor 8 tiene mínimos estrictos.

Herramienta Requisito de Capacitor 8
Node.js 22 o superior
JDK 21
Xcode 26 o superior
Target de despliegue de iOS 15.0
Android minSdkVersion / targetSdkVersion 24 / 36

Solución: fijar cada versión explícitamente en el pipeline en lugar de confiar latest:

- uses: actions/setup-node@v6
  with:
    node-version-file: .nvmrc
- uses: actions/setup-java@v4
  with:
    distribution: temurin
    java-version: '21'
- uses: maxim-lobanov/setup-xcode@v1
  with:
    xcode-version: '26'

Error: compilar con una versión de Xcode que Apple ya no acepta

Síntoma: el upload falla con un mensaje indicando que la app se compiló con un SDK no compatible.

Causa: desde el 28 de abril de 2026, App Store Connect requiere Xcode 26 y el SDK de iOS 26. Los Macs self-hosted y las imágenes de runner antiguas siguen usando Xcode 16 por defecto.

Solución: selecciona una macos-26 imagen o instala Xcode 26 en tus runners. Detalles en el requisito de Xcode 26 de Apple para apps Capacitor.

Error común: confusión entre CocoaPods y SPM

Síntoma: xcodebuild: error: 'App.xcworkspace' does not exist, o no se encuentran los pods.

Causa: los nuevos proyectos de Capacitor 8 usan Swift Package Manager y compilan ios/App/App.xcodeproj. Los proyectos antiguos usan CocoaPods y compilan ios/App/App.xcworkspace después de pod install. Los pipelines copiados de tutoriales viejos asumen el workspace.

Solución: revisa cuál usa tu proyecto y compila el archivo correcto. Si estás migrando, consulta Cómo migrar tu app de Capacitor a SPM.

Error común: los builds de plugins de Android fallan tras actualizar Gradle

Síntoma: Namespace not specified, package attribute is deprecated, o errores de un plugin build.gradle tras actualizar Android Studio.

Solución: fija el Android Gradle Plugin en android/build.gradle, sube a una rama y actualiza plugins primero. Ver errores específicos en Corrige errores de build del plugin Capacitor con AGP 9.

Etapa 3: firma de Code

Error común: el signing de iOS solo funciona en tu Mac

Síntoma: No signing certificate "iOS Distribution" found, No profiles for 'com.example.app' were found, o errSecInternalComponent.

Causa: el keychain de tu Mac contiene el certificado y Xcode descarga los profiles por ti. Un runner de CI no tiene ninguno y su keychain está bloqueado en una sesión no interactiva.

Solución: importa el certificado en un keychain temporal desbloqueado e instala el profile donde Xcode 16 y versiones posteriores lo buscan:

security create-keychain -p "$KEYCHAIN_PASSWORD" ci.keychain
security set-keychain-settings -lut 21600 ci.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" ci.keychain
security import dist.p12 -k ci.keychain -P "$P12_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" ci.keychain
security list-keychains -d user -s ci.keychain login.keychain

PROFILES="$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles"
mkdir -p "$PROFILES"
cp app.mobileprovision "$PROFILES/"

Usa el nombre de la identidad Apple Distribution, no el legacy iOS Distribution. El de fastlane setup_ci además de match automatiza esto. Capgo Build toma el certificado y el profile como variables de entorno y gestiona el keychain en sus propias máquinas, así que el job de Linux nunca toca security.

Error común: certificados expirados o revocados

Síntoma: un pipeline que funcionó un año falla de la noche a la mañana.

Causa: Los certificados de distribución de Apple y los provisioning profiles caducan al año. Si un compañero crea un nuevo certificado en Xcode, también puede invalidar el profile que usa tu CI.

Solución: añade las fechas de vencimiento al calendario de tu equipo, usa un certificado de distribución compartido para el CI y verifica todo antes de hacer el build. bunx @capgo/cli@latest build prescan --platform ios verifica la expiración del certificado, la contraseña y el emparejamiento del perfil antes de subir nada.

Error común: inicios de sesión de Apple ID y prompts de doble factor

Síntoma: fastlane se queda colgado esperando un code de 6 dígitos.

Solución: usa una clave API de App Store Connect (.p8, key ID, issuer ID) en lugar de un Apple ID y contraseña. No requiere autenticación de dos factores y puede limitarse al rol de App Manager. Si la autenticación falla con una clave correcta, revisa el reloj del runner: el token se firma con la hora local y Apple rechaza tokens con un timestamp desfasado.

Error común: secretos en base64 que no se decodifican

Síntoma: MAC verification failed, invalid keystore format, o base64: invalid input.

Causa: saltos de línea al copiar el valor base64, o un .p12 creado con los valores predeterminados de OpenSSL 3 que macOS no puede leer.

Solución: codificar en una sola línea, y usar -legacy al crear un .p12 con OpenSSL 3:

base64 -i dist.p12 | tr -d '\n' > dist.p12.b64
openssl pkcs12 -export -legacy -inkey key.pem -in cert.pem -out dist.p12

Error común: perder el keystore de Android

Síntoma: no puedes firmar una actualización porque nadie tiene el keystore.

Solución: Con Play App Signing, el keystore es la clave de carga, y el soporte de Play Console puede registrar una nueva. Guarda el keystore en los secrets de tu CI y en un backup offline. Nunca dejes que solo esté en un portátil. El Generador de keystore para Android crea uno nuevo si empiezas de cero.

Etapa 4: Diseño del pipeline

Error común: builds nativos en cada pull request

Síntoma: checks de PR lentos y una factura enorme de minutos en macOS.

Causa: Los builds de iOS en runners de macOS alojados son los minutos más caros en la mayoría de planes de CI, y la mayoría de los commits solo tocan JavaScript.

Solución: ejecuta lint, tests y el build web en cada PR. Ejecuta builds nativos en tags de release o merges a main. Para previews de PR, envía el bundle web a un canal de Capgo en lugar de compilar un binario, como se describe en Comparativa de plataformas CI/CD para apps Capacitor.

Error común: compilar iOS y Android de forma secuencial

Solución: usa una matriz para compilar ambas plataformas en paralelo, y configura fail-fast: false para que un error de firma en iOS no cancele un build de Android correcto.

strategy:
  fail-fast: false
  matrix:
    platform: [ios, android]

Error común: sin caché, o con la caché incorrecta

Síntoma: cada build descarga las dependencias de Gradle y CocoaPods desde cero, o un build de staging incluye config de producción desde una caché obsoleta.

Solución: caché ~/.gradle/caches, ~/.gradle/wrapper, y ios/App/Pods basada en los lockfiles. Divide las cachés por entorno. Con Capgo Build, la caché de build por app se puede dividir con --cache-key prod y --cache-key staging, o se omite con --no-cache para un build limpio.

Error común: rutas de monorepo

Síntoma: could not find capacitor.config o plugins que faltan en el proyecto nativo.

Solución: ejecuta comandos de Capacitor desde el paquete de la app y apunta las herramientas a hoisted node_modules. El Capgo CLI acepta --path y --node-modules para esto.

Etapa 5: Envío a la tienda

Error común: reutilizar números de build

Síntoma: “The bundle version must be higher than the previously uploaded version” en iOS, o “Version code has already been used” en Google Play.

Solución: genera el número en tu CI. Con fastlane, lee el último build de TestFlight y súmale uno. Con Capgo Build, esto es lo predeterminado: obtiene el último número de build de App Store Connect o el más alto versionCode de Google Play y lo incrementa.

Error común: builds trabados en TestFlight

Síntoma: el upload es exitoso pero los testers nunca ven el build.

Causa: missing export compliance answer.

Solución: decláralo una vez en ios/App/App/Info.plist si solo usas cifrado estándar:

<key>ITSAppUsesNonExemptEncryption</key>
<false/>

Error común: rechazos por privacy manifest

Síntoma: email de Apple sobre declaraciones de API de motivo requerido que faltan (ITMS-91053).

Solución: añade un PrivacyInfo.xcprivacy al target de la app y actualiza los plugins que incluyan el suyo. Consulta la guía de privacy manifest para apps de iOS.

Error común: tipo de artifact incorrecto

Solución: Google Play requiere un AAB (bundleRelease), no un APK. bundleRelease sigue teniendo éxito sin una release configuración de firma y genera un AAB sin firmar que Play rechaza, así que configura signingConfigs.release en android/app/build.gradle primero. iOS requiere un export para App Store, no un IPA de desarrollo o ad hoc. Revisa el método de exportación en tu build step.

Etapa 6: Live updates

Error común: lanzar un live update que requiere un nuevo Native Build

Síntoma: tras una actualización OTA, la app falla al llamar a un método de un plugin que no existe en el binario instalado.

Causa: el bundle web depende de una versión de plugin más reciente que la que se compiló en la app de los dispositivos de los usuarios.

Solución: deja que el pipeline decida. build needed devuelve 0 cuando las dependencias nativas coinciden con lo que está activo en el channel y 1 cuando se requiere un nuevo binario:

if bunx @capgo/cli@latest build needed com.example.app --channel production; then
  bunx @capgo/cli@latest bundle upload com.example.app --channel production
else
  bunx cap sync
  bunx @capgo/cli@latest build request com.example.app --platform ios
  bunx @capgo/cli@latest build request com.example.app --platform android
fi

También fuerza la ruta nativa cuando los archivos en ios/, android/, o capacitor.config.* cambian. El patrón completo está en Elige automáticamente live update o Native Build, y las reglas de compatibilidad están en compatibilidad nativa.

La solución que elimina la mayoría de los errores

Si solo vas a cambiar una cosa, saca la compilación y la firma de iOS de tus runners de CI. La configuración de Keychain, las actualizaciones de Xcode, los costes de macOS y la instalación de perfiles desaparecen de tu pipeline cuando un job de Linux le pasa el proyecto preparado a Build de Capgo:

bun install --frozen-lockfile && bun run build
bunx cap sync ios
bunx @capgo/cli@latest build request com.example.app --platform ios --build-mode release

Los errores en las etapas 1, 5 y 6 siguen presentes, porque tienen que ver con tu proyecto, no con el runner. Para más info sobre cómo debuggear jobs fallidos, consulta Cómo solucionar fallos de build en pipelines de CI/CD de Capacitor.

Referencia rápida

Error Error común Sección
UI antigua en un nuevo build cap sync antes del build web Etapa 1
No profiles for ... were found Perfil no instalado o incompatible Etapa 3
errSecInternalComponent Keychain bloqueado Etapa 3
MAC verification failed Contraseña incorrecta u OpenSSL 3 .p12 Etapa 3
Unsupported class file major version JDK incorrecto Etapa 2
SDK demasiado antiguo al subir Xcode anterior a la versión 26 Etapa 2
La versión del bundle debe ser mayor Número de build reutilizado Etapa 5
La versión code ya está en uso Reutilizado versionCode Etapa 5
Crash tras live update Cambio nativo enviado vía OTA Etapa 6
Live updates para apps Capacitor

Cuando un bug de la capa web está en producción, publica el fix a través de Capgo en lugar de esperar días a la aprobación de la app store. Los usuarios reciben la actualización en segundo plano mientras los cambios nativos siguen el flujo de revisión habitual.

Lo último de nuestro blog

Capgo te da la información clave que necesitas para crear una app móvil realmente profesional.