De Cordova a Capacitor: Guía Completa de Migración para Aplicaciones Móviles

6 de enero de 2026
Osman Jimenez
Cordova Capacitor Migración Desarrollo Móvil

La Gran Migración: De Cordova a Capacitor

Apache Cordova ha sido durante años el estándar de facto para el desarrollo de aplicaciones móviles híbridas. Sin embargo, la evolución del ecosistema móvil y las limitaciones inherentes de Cordova han llevado a muchos equipos a considerar Capacitor como su sucesor natural. En esta guía completa, exploramos el proceso de migración, las diferencias clave y las mejores prácticas para una transición exitosa.

¿Por qué Migrar de Cordova a Capacitor?

Limitaciones de Cordova

  • Arquitectura monolítica: Dificulta la modularización
  • Dependencia de plugins: Ecosistema fragmentado
  • Configuración compleja: XML verboso y propenso a errores
  • Debugging limitado: Herramientas de desarrollo básicas
  • Performance: Overhead del bridge JavaScript-nativo

Ventajas de Capacitor

  • Arquitectura moderna: Basada en web APIs estándar
  • Desarrollo nativo: Acceso directo a proyectos iOS/Android
  • TypeScript first: Mejor experiencia de desarrollo
  • Hot reload: Desarrollo más ágil
  • Plugins modernos: APIs consistentes y bien documentadas

Análisis Pre-Migración

Auditoría del Proyecto Cordova

# Script de análisis de proyecto Cordova
#!/bin/bash

echo "=== ANÁLISIS DE PROYECTO CORDOVA ==="
echo

# Información básica
echo "📱 Plataformas instaladas:"
cordova platform list
echo

# Plugins instalados
echo "🔌 Plugins instalados:"
cordova plugin list
echo

# Configuración
echo "⚙️  Configuración (config.xml):"
if [ -f "config.xml" ]; then
    echo "✅ config.xml encontrado"
    grep -E "<(widget|name|description|platform|plugin)" config.xml
else
    echo "❌ config.xml no encontrado"
fi
echo

# Estructura de archivos
echo "📁 Estructura del proyecto:"
tree -L 2 -I 'node_modules|platforms|plugins'
echo

# Dependencias
echo "📦 Dependencias (package.json):"
if [ -f "package.json" ]; then
    echo "✅ package.json encontrado"
    jq '.dependencies, .devDependencies' package.json 2>/dev/null || echo "Error leyendo package.json"
else
    echo "❌ package.json no encontrado"
fi

Mapeo de Plugins Cordova → Capacitor

// plugin-migration-map.ts
export const PLUGIN_MIGRATION_MAP = {
  // Plugins oficiales con equivalentes directos
  'cordova-plugin-camera': '@capacitor/camera',
  'cordova-plugin-geolocation': '@capacitor/geolocation',
  'cordova-plugin-device': '@capacitor/device',
  'cordova-plugin-network-information': '@capacitor/network',
  'cordova-plugin-statusbar': '@capacitor/status-bar',
  'cordova-plugin-splashscreen': '@capacitor/splash-screen',
  'cordova-plugin-keyboard': '@capacitor/keyboard',
  'cordova-plugin-file': '@capacitor/filesystem',
  'cordova-plugin-media': '@capacitor/media',
  'cordova-plugin-dialogs': '@capacitor/dialog',
  'cordova-plugin-vibration': '@capacitor/haptics',
  'cordova-plugin-battery-status': '@capacitor/battery',
  'cordova-plugin-contacts': '@capacitor/contacts',
  
  // Plugins que requieren alternativas
  'cordova-plugin-inappbrowser': '@capacitor/browser',
  'cordova-plugin-local-notification': '@capacitor/local-notifications',
  'phonegap-plugin-push': '@capacitor/push-notifications',
  'cordova-plugin-file-transfer': 'Custom HTTP implementation',
  'cordova-plugin-whitelist': 'Capacitor security model',
  
  // Plugins sin equivalente directo
  'cordova-plugin-crosswalk-webview': 'Not needed (modern WebView)',
  'cordova-plugin-wkwebview-engine': 'Default in Capacitor iOS',
  'cordova-plugin-ionic-webview': 'Built into Capacitor',
  
  // Plugins de terceros populares
  'cordova-plugin-googlemaps': '@capacitor-community/googlemaps',
  'cordova-plugin-firebase': '@capacitor-firebase/*',
  'cordova-plugin-facebook4': '@capacitor-community/facebook-login',
  'cordova-plugin-googleplus': '@codetrix-studio/capacitor-google-auth',
  'cordova-sqlite-storage': '@capacitor-community/sqlite'
};

// Función para analizar plugins
export function analyzePlugins(cordovaPlugins: string[]): PluginAnalysis {
  const analysis: PluginAnalysis = {
    directMigration: [],
    requiresAlternative: [],
    noEquivalent: [],
    customImplementation: []
  };
  
  cordovaPlugins.forEach(plugin => {
    const capacitorEquivalent = PLUGIN_MIGRATION_MAP[plugin];
    
    if (!capacitorEquivalent) {
      analysis.noEquivalent.push(plugin);
    } else if (capacitorEquivalent.startsWith('@capacitor/')) {
      analysis.directMigration.push({
        cordova: plugin,
        capacitor: capacitorEquivalent
      });
    } else if (capacitorEquivalent.includes('Custom')) {
      analysis.customImplementation.push(plugin);
    } else {
      analysis.requiresAlternative.push({
        cordova: plugin,
        capacitor: capacitorEquivalent
      });
    }
  });
  
  return analysis;
}

interface PluginAnalysis {
  directMigration: Array<{cordova: string, capacitor: string}>;
  requiresAlternative: Array<{cordova: string, capacitor: string}>;
  noEquivalent: string[];
  customImplementation: string[];
}

Proceso de Migración Paso a Paso

Fase 1: Preparación del Entorno

# 1. Backup del proyecto actual
cp -r my-cordova-app my-cordova-app-backup

# 2. Instalar Capacitor CLI
npm install -g @capacitor/cli

# 3. Verificar versiones
node --version  # >= 16
npm --version   # >= 8
ionic --version # Si usas Ionic

# 4. Crear rama de migración
git checkout -b migration/cordova-to-capacitor
git add .
git commit -m "Backup before Capacitor migration"

Fase 2: Inicialización de Capacitor

# En el directorio del proyecto Cordova
npm install @capacitor/core @capacitor/cli

# Inicializar Capacitor
npx cap init "My App" "com.company.myapp" --web-dir="www"

# Instalar plataformas
npm install @capacitor/ios @capacitor/android
npx cap add ios
npx cap add android

Fase 3: Configuración de Capacitor

// capacitor.config.ts
import { CapacitorConfig } from '@capacitor/cli';

// Migrar configuración desde config.xml
const config: CapacitorConfig = {
  appId: 'com.company.myapp', // De config.xml widget id
  appName: 'My App',          // De config.xml name
  webDir: 'www',              // Directorio de build
  server: {
    androidScheme: 'https'
  },
  plugins: {
    // Migrar preferencias de Cordova
    SplashScreen: {
      launchShowDuration: 2000,
      backgroundColor: '#ffffff',
      showSpinner: false
    },
    StatusBar: {
      style: 'default',
      backgroundColor: '#000000'
    },
    Keyboard: {
      resize: 'body',
      style: 'dark'
    }
  },
  // Configuración específica por plataforma
  ios: {
    scheme: 'My App',
    contentInset: 'automatic'
  },
  android: {
    allowMixedContent: true,
    captureInput: true,
    webContentsDebuggingEnabled: true
  }
};

export default config;

Fase 4: Migración de Plugins

# Instalar plugins de Capacitor equivalentes
npm install @capacitor/camera @capacitor/geolocation @capacitor/device
npm install @capacitor/network @capacitor/status-bar @capacitor/splash-screen
npm install @capacitor/keyboard @capacitor/filesystem @capacitor/dialog

# Plugins de la comunidad si es necesario
npm install @capacitor-community/sqlite
npm install @capacitor-community/googlemaps

Migración de Código

Ejemplo: Migración del Plugin de Cámara

// ANTES (Cordova)
function takePicture() {
  const options = {
    quality: 75,
    destinationType: Camera.DestinationType.FILE_URI,
    sourceType: Camera.PictureSourceType.CAMERA,
    encodingType: Camera.EncodingType.JPEG,
    targetWidth: 300,
    targetHeight: 300
  };
  
  navigator.camera.getPicture(
    function(imageURI) {
      console.log('Image URI: ' + imageURI);
      displayImage(imageURI);
    },
    function(error) {
      console.error('Camera error: ' + error);
    },
    options
  );
}

// DESPUÉS (Capacitor)
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';

async function takePicture() {
  try {
    const image = await Camera.getPhoto({
      quality: 75,
      resultType: CameraResultType.Uri,
      source: CameraSource.Camera,
      width: 300,
      height: 300
    });
    
    console.log('Image URI:', image.webPath);
    displayImage(image.webPath!);
    
  } catch (error) {
    console.error('Camera error:', error);
  }
}

Ejemplo: Migración de Geolocalización

// ANTES (Cordova)
function getCurrentLocation() {
  const options = {
    enableHighAccuracy: true,
    timeout: 10000,
    maximumAge: 60000
  };
  
  navigator.geolocation.getCurrentPosition(
    function(position) {
      const lat = position.coords.latitude;
      const lng = position.coords.longitude;
      console.log('Location:', lat, lng);
    },
    function(error) {
      console.error('Location error:', error.message);
    },
    options
  );
}

// DESPUÉS (Capacitor)
import { Geolocation } from '@capacitor/geolocation';

async function getCurrentLocation() {
  try {
    const position = await Geolocation.getCurrentPosition({
      enableHighAccuracy: true,
      timeout: 10000
    });
    
    const lat = position.coords.latitude;
    const lng = position.coords.longitude;
    console.log('Location:', lat, lng);
    
  } catch (error) {
    console.error('Location error:', error.message);
  }
}

Migración de Almacenamiento Local

// ANTES (Cordova con cordova-plugin-file)
function writeFile(fileName, data) {
  window.resolveLocalFileSystemURL(
    cordova.file.dataDirectory,
    function(dirEntry) {
      dirEntry.getFile(fileName, { create: true }, function(fileEntry) {
        fileEntry.createWriter(function(fileWriter) {
          fileWriter.write(new Blob([data], { type: 'text/plain' }));
        });
      });
    }
  );
}

// DESPUÉS (Capacitor)
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';

async function writeFile(fileName: string, data: string) {
  try {
    await Filesystem.writeFile({
      path: fileName,
      data: data,
      directory: Directory.Data,
      encoding: Encoding.UTF8
    });
    
    console.log('File written successfully');
  } catch (error) {
    console.error('Error writing file:', error);
  }
}

async function readFile(fileName: string): Promise {
  try {
    const result = await Filesystem.readFile({
      path: fileName,
      directory: Directory.Data,
      encoding: Encoding.UTF8
    });
    
    return result.data as string;
  } catch (error) {
    console.error('Error reading file:', error);
    throw error;
  }
}

Migración de Configuración Nativa

iOS: Info.plist

<!-- ANTES (Cordova) -->
<!-- En config.xml -->
<platform name="ios">
    <edit-config target="*-Info.plist" file="*-Info.plist" mode="merge">
        <dict>
            <key>NSCameraUsageDescription</key>
            <string>This app needs access to camera to take photos</string>
            <key>NSLocationWhenInUseUsageDescription</key>
            <string>This app needs location access</string>
        </dict>
    </edit-config>
</platform>

<!-- DESPUÉS (Capacitor) -->
<!-- En ios/App/App/Info.plist -->
<key>NSCameraUsageDescription</key>
<string>This app needs access to camera to take photos</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs location access</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs access to photo library</string>

Android: Permisos y Configuración

<!-- ANTES (Cordova) -->
<!-- En config.xml -->
<platform name="android">
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</platform>

<!-- DESPUÉS (Capacitor) -->
<!-- En android/app/src/main/AndroidManifest.xml -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<!-- Configuración adicional en android/app/build.gradle -->
android {
    compileSdkVersion 34
    
    defaultConfig {
        minSdkVersion 22
        targetSdkVersion 34
    }
}

Herramientas de Migración Automática

Script de Migración Personalizado

#!/usr/bin/env node
// migration-script.js

const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

class CordovaToCapacitorMigrator {
  constructor(projectPath) {
    this.projectPath = projectPath;
    this.configXmlPath = path.join(projectPath, 'config.xml');
    this.packageJsonPath = path.join(projectPath, 'package.json');
  }

  async migrate() {
    console.log('🚀 Iniciando migración de Cordova a Capacitor...');
    
    try {
      await this.analyzeProject();
      await this.setupCapacitor();
      await this.migratePlugins();
      await this.migrateConfiguration();
      await this.generateMigrationReport();
      
      console.log('✅ Migración completada exitosamente!');
    } catch (error) {
      console.error('❌ Error durante la migración:', error.message);
      process.exit(1);
    }
  }

  async analyzeProject() {
    console.log('📊 Analizando proyecto Cordova...');
    
    // Leer config.xml
    if (fs.existsSync(this.configXmlPath)) {
      const configXml = fs.readFileSync(this.configXmlPath, 'utf8');
      this.extractConfigData(configXml);
    }
    
    // Leer package.json
    if (fs.existsSync(this.packageJsonPath)) {
      const packageJson = JSON.parse(fs.readFileSync(this.packageJsonPath, 'utf8'));
      this.extractDependencies(packageJson);
    }
    
    // Analizar plugins instalados
    this.analyzeInstalledPlugins();
  }

  extractConfigData(configXml) {
    // Extraer información básica
    const widgetMatch = configXml.match(/]*id="([^"]*)"/);;
    const nameMatch = configXml.match(/([^<]*)<\/name>/);
    const descriptionMatch = configXml.match(/([^<]*)<\/description>/);
    
    this.appConfig = {
      appId: widgetMatch ? widgetMatch[1] : 'com.example.app',
      appName: nameMatch ? nameMatch[1] : 'My App',
      description: descriptionMatch ? descriptionMatch[1] : ''
    };
    
    console.log('📱 App ID:', this.appConfig.appId);
    console.log('📱 App Name:', this.appConfig.appName);
  }

  extractDependencies(packageJson) {
    this.cordovaPlugins = [];
    
    // Buscar plugins de Cordova en dependencias
    const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies };
    
    Object.keys(allDeps).forEach(dep => {
      if (dep.startsWith('cordova-plugin-') || dep.includes('phonegap')) {
        this.cordovaPlugins.push(dep);
      }
    });
    
    console.log('🔌 Plugins Cordova encontrados:', this.cordovaPlugins.length);
  }

  analyzeInstalledPlugins() {
    try {
      const pluginList = execSync('cordova plugin list', { encoding: 'utf8' });
      console.log('📋 Plugins instalados:');
      console.log(pluginList);
    } catch (error) {
      console.log('⚠️  No se pudo obtener la lista de plugins instalados');
    }
  }

  async setupCapacitor() {
    console.log('⚙️  Configurando Capacitor...');
    
    // Instalar Capacitor
    execSync('npm install @capacitor/core @capacitor/cli', { stdio: 'inherit' });
    
    // Inicializar Capacitor
    const initCommand = `npx cap init "${this.appConfig.appName}" "${this.appConfig.appId}" --web-dir="www"`;
    execSync(initCommand, { stdio: 'inherit' });
    
    // Agregar plataformas
    execSync('npm install @capacitor/ios @capacitor/android', { stdio: 'inherit' });
    execSync('npx cap add ios', { stdio: 'inherit' });
    execSync('npx cap add android', { stdio: 'inherit' });
  }

  async migratePlugins() {
    console.log('🔄 Migrando plugins...');
    
    const pluginMigrations = this.getPluginMigrations();
    const capacitorPlugins = [];
    
    for (const cordovaPlugin of this.cordovaPlugins) {
      const capacitorEquivalent = pluginMigrations[cordovaPlugin];
      
      if (capacitorEquivalent && capacitorEquivalent.startsWith('@capacitor')) {
        capacitorPlugins.push(capacitorEquivalent);
        console.log(`✅ ${cordovaPlugin} → ${capacitorEquivalent}`);
      } else {
        console.log(`⚠️  ${cordovaPlugin} requiere migración manual`);
      }
    }
    
    // Instalar plugins de Capacitor
    if (capacitorPlugins.length > 0) {
      const installCommand = `npm install ${capacitorPlugins.join(' ')}`;
      execSync(installCommand, { stdio: 'inherit' });
    }
  }

  getPluginMigrations() {
    return {
      'cordova-plugin-camera': '@capacitor/camera',
      'cordova-plugin-geolocation': '@capacitor/geolocation',
      'cordova-plugin-device': '@capacitor/device',
      'cordova-plugin-network-information': '@capacitor/network',
      'cordova-plugin-statusbar': '@capacitor/status-bar',
      'cordova-plugin-splashscreen': '@capacitor/splash-screen',
      'cordova-plugin-keyboard': '@capacitor/keyboard',
      'cordova-plugin-file': '@capacitor/filesystem',
      'cordova-plugin-dialogs': '@capacitor/dialog',
      'cordova-plugin-vibration': '@capacitor/haptics'
    };
  }

  async migrateConfiguration() {
    console.log('📝 Generando configuración de Capacitor...');
    
    const capacitorConfig = `import { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: '${this.appConfig.appId}',
  appName: '${this.appConfig.appName}',
  webDir: 'www',
  server: {
    androidScheme: 'https'
  },
  plugins: {
    SplashScreen: {
      launchShowDuration: 2000,
      backgroundColor: '#ffffff'
    },
    StatusBar: {
      style: 'default'
    }
  }
};

export default config;`;
    
    fs.writeFileSync('capacitor.config.ts', capacitorConfig);
    console.log('✅ Configuración de Capacitor generada');
  }

  async generateMigrationReport() {
    console.log('📊 Generando reporte de migración...');
    
    const report = `# Reporte de Migración: Cordova → Capacitor

## Información del Proyecto
- **App ID:** ${this.appConfig.appId}
- **App Name:** ${this.appConfig.appName}
- **Fecha de migración:** ${new Date().toISOString()}

## Plugins Migrados
${this.cordovaPlugins.map(plugin => `- ${plugin}`).join('\n')}

## Próximos Pasos
1. Revisar y actualizar el código que usa plugins
2. Probar la aplicación en dispositivos
3. Actualizar scripts de build
4. Configurar CI/CD para Capacitor

## Recursos
- [Documentación de Capacitor](https://capacitorjs.com/docs)
- [Guía de migración](https://capacitorjs.com/docs/cordova/migrating-from-cordova-to-capacitor)
`;
    
    fs.writeFileSync('MIGRATION_REPORT.md', report);
    console.log('✅ Reporte guardado en MIGRATION_REPORT.md');
  }
}

// Ejecutar migración
if (require.main === module) {
  const projectPath = process.argv[2] || process.cwd();
  const migrator = new CordovaToCapacitorMigrator(projectPath);
  migrator.migrate();
}

module.exports = CordovaToCapacitorMigrator;

Testing y Validación

Checklist de Migración

// migration-checklist.ts
export interface MigrationChecklist {
  setup: ChecklistItem[];
  plugins: ChecklistItem[];
  configuration: ChecklistItem[];
  testing: ChecklistItem[];
  deployment: ChecklistItem[];
}

interface ChecklistItem {
  task: string;
  completed: boolean;
  notes?: string;
}

export const MIGRATION_CHECKLIST: MigrationChecklist = {
  setup: [
    { task: 'Backup del proyecto Cordova', completed: false },
    { task: 'Instalar Capacitor CLI', completed: false },
    { task: 'Inicializar proyecto Capacitor', completed: false },
    { task: 'Agregar plataformas iOS/Android', completed: false }
  ],
  
  plugins: [
    { task: 'Mapear plugins Cordova → Capacitor', completed: false },
    { task: 'Instalar plugins de Capacitor', completed: false },
    { task: 'Migrar código de plugins', completed: false },
    { task: 'Probar funcionalidad de plugins', completed: false }
  ],
  
  configuration: [
    { task: 'Migrar config.xml → capacitor.config.ts', completed: false },
    { task: 'Configurar permisos iOS (Info.plist)', completed: false },
    { task: 'Configurar permisos Android (AndroidManifest.xml)', completed: false },
    { task: 'Actualizar scripts de build', completed: false }
  ],
  
  testing: [
    { task: 'Probar en simulador iOS', completed: false },
    { task: 'Probar en emulador Android', completed: false },
    { task: 'Probar en dispositivos físicos', completed: false },
    { task: 'Validar funcionalidad crítica', completed: false },
    { task: 'Probar performance', completed: false }
  ],
  
  deployment: [
    { task: 'Configurar signing iOS', completed: false },
    { task: 'Configurar signing Android', completed: false },
    { task: 'Actualizar CI/CD pipeline', completed: false },
    { task: 'Generar builds de producción', completed: false },
    { task: 'Subir a App Store/Play Store', completed: false }
  ]
};

Tests Automatizados

// migration.test.ts
import { describe, it, expect, beforeAll } from '@jest/jest';
import { Camera } from '@capacitor/camera';
import { Geolocation } from '@capacitor/geolocation';
import { Device } from '@capacitor/device';

describe('Capacitor Plugin Migration Tests', () => {
  
  describe('Camera Plugin', () => {
    it('should be available', () => {
      expect(Camera).toBeDefined();
      expect(typeof Camera.getPhoto).toBe('function');
    });
    
    it('should have correct permissions', async () => {
      const permissions = await Camera.checkPermissions();
      expect(permissions).toHaveProperty('camera');
      expect(permissions).toHaveProperty('photos');
    });
  });
  
  describe('Geolocation Plugin', () => {
    it('should be available', () => {
      expect(Geolocation).toBeDefined();
      expect(typeof Geolocation.getCurrentPosition).toBe('function');
    });
    
    it('should check permissions', async () => {
      const permissions = await Geolocation.checkPermissions();
      expect(permissions).toHaveProperty('location');
    });
  });
  
  describe('Device Plugin', () => {
    it('should get device info', async () => {
      const info = await Device.getInfo();
      expect(info).toHaveProperty('platform');
      expect(info).toHaveProperty('model');
      expect(info).toHaveProperty('operatingSystem');
    });
  });
});

Optimización Post-Migración

Performance Tuning

// capacitor.config.ts - Configuración optimizada
import { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'com.company.myapp',
  appName: 'My App',
  webDir: 'www',
  server: {
    androidScheme: 'https',
    // Optimización para desarrollo
    hostname: 'localhost',
    iosScheme: 'capacitor'
  },
  plugins: {
    // Optimizaciones de splash screen
    SplashScreen: {
      launchShowDuration: 1500,
      backgroundColor: '#ffffff',
      showSpinner: false,
      androidSpinnerStyle: 'large',
      iosSpinnerStyle: 'small',
      spinnerColor: '#999999',
      splashFullScreen: true,
      splashImmersive: true
    },
    
    // Optimizaciones de teclado
    Keyboard: {
      resize: 'body',
      style: 'dark',
      resizeOnFullScreen: true
    },
    
    // Configuración de status bar
    StatusBar: {
      style: 'default',
      backgroundColor: '#000000',
      overlaysWebView: false
    }
  },
  
  // Configuración específica iOS
  ios: {
    scheme: 'My App',
    contentInset: 'automatic',
    scrollEnabled: true,
    backgroundColor: '#ffffff'
  },
  
  // Configuración específica Android
  android: {
    allowMixedContent: true,
    captureInput: true,
    webContentsDebuggingEnabled: false, // Solo en desarrollo
    backgroundColor: '#ffffff',
    loggingBehavior: 'none' // Producción
  }
};

export default config;

Scripts de Build Optimizados

// package.json - Scripts actualizados
{
  "scripts": {
    // Scripts de desarrollo
    "dev": "ionic serve",
    "dev:ios": "ionic capacitor run ios --livereload --external",
    "dev:android": "ionic capacitor run android --livereload --external",
    
    // Scripts de build
    "build": "ionic build --prod",
    "build:ios": "ionic build --prod && npx cap sync ios && npx cap open ios",
    "build:android": "ionic build --prod && npx cap sync android && npx cap open android",
    
    // Scripts de sincronización
    "sync": "npx cap sync",
    "sync:ios": "npx cap sync ios",
    "sync:android": "npx cap sync android",
    
    // Scripts de limpieza
    "clean": "rm -rf www && rm -rf node_modules && npm install",
    "clean:ios": "npx cap clean ios",
    "clean:android": "npx cap clean android",
    
    // Scripts de testing
    "test": "jest",
    "test:e2e": "cypress run",
    "test:migration": "jest --testPathPattern=migration"
  }
}

Troubleshooting Común

Problemas Frecuentes y Soluciones

// troubleshooting-guide.ts
export const COMMON_ISSUES = {
  
  // Error: Plugin no encontrado
  'Plugin not found': {
    problem: 'Plugin de Capacitor no está registrado',
    solution: `
      1. Verificar instalación: npm list @capacitor/plugin-name
      2. Sincronizar: npx cap sync
      3. Limpiar y rebuild: npx cap clean && npx cap sync
    `
  },
  
  // Error: Permisos
  'Permission denied': {
    problem: 'Permisos no configurados correctamente',
    solution: `
      iOS: Agregar keys en Info.plist
      Android: Agregar permissions en AndroidManifest.xml
      Verificar: await Plugin.checkPermissions()
    `
  },
  
  // Error: Build iOS
  'iOS build failed': {
    problem: 'Problemas de configuración iOS',
    solution: `
      1. Abrir Xcode: npx cap open ios
      2. Verificar signing & capabilities
      3. Limpiar build folder en Xcode
      4. Verificar deployment target
    `
  },
  
  // Error: Build Android
  'Android build failed': {
    problem: 'Problemas de configuración Android',
    solution: `
      1. Verificar Android SDK
      2. Limpiar: ./gradlew clean
      3. Verificar build.gradle
      4. Sincronizar: npx cap sync android
    `
  },
  
  // Error: Live reload
  'Live reload not working': {
    problem: 'Hot reload no funciona en dispositivo',
    solution: `
      1. Verificar que dispositivo y PC estén en misma red
      2. Usar --external flag
      3. Verificar firewall
      4. Usar IP específica en capacitor.config.ts
    `
  }
};

Mejores Prácticas Post-Migración

1. Estructura de Proyecto

  • Separar lógica nativa: Mantén código nativo en carpetas específicas
  • Versionado: Incluye carpetas ios/ y android/ en git
  • Configuración: Usa variables de entorno para diferentes builds

2. Desarrollo

  • Hot reload: Usa live reload para desarrollo ágil
  • Debugging: Aprovecha las herramientas nativas
  • Testing: Prueba en dispositivos reales regularmente

3. Deployment

  • CI/CD: Automatiza builds y deployments
  • Code signing: Gestiona certificados correctamente
  • Versionado: Sincroniza versiones entre web y nativo

Conclusión

La migración de Cordova a Capacitor representa una evolución natural hacia un desarrollo móvil más moderno y eficiente. Aunque el proceso requiere planificación cuidadosa y testing exhaustivo, los beneficios en términos de performance, experiencia de desarrollo y mantenibilidad justifican ampliamente el esfuerzo.

Capacitor no solo resuelve las limitaciones técnicas de Cordova, sino que también abre nuevas posibilidades para el desarrollo híbrido, acercándolo más a la experiencia nativa sin sacrificar la productividad del desarrollo web.

La clave del éxito está en una migración gradual, testing continuo y aprovechamiento de las nuevas capacidades que ofrece Capacitor para crear aplicaciones móviles de calidad superior.

¿Has migrado ya de Cordova a Capacitor? ¿Qué desafíos encontraste en el proceso? Comparte tu experiencia en los comentarios.