React NativeExpoMetroJSITurboModulesFabricCodegenHermesMobile Development

React Native + Expo + Metro + Implementación Nativa: Guía Completa

Sloth255
Sloth255
·9 min read·1,965 words

Cuando se empieza a usar React Native, es fácil tener la sensación de que "funciona sin saber muy bien por qué". En este artículo explicaremos todo lo que ocurre bajo el capó — desde Metro, JSI, Codegen y cómo funciona Expo, hasta la implementación nativa para áreas que el SDK de Expo no puede cubrir.


1. La Filosofía Central de React Native

El corazón de React Native es traducir la lógica UI escrita en JS a componentes UI nativos. Al igual que ReactDOM convierte <div> en elementos DOM, React Native mapea <View> y <Text> a UIView en iOS y android.view.View en Android.

Esto significa que genera UI nativa real — no renderiza HTML en una WebView. Esa es la diferencia fundamental con Cordova o Ionic.


2. Evolución de la Arquitectura: De Bridge a JSI

React Native actualmente tiene dos arquitecturas coexistentes.

flowchart LR
  subgraph OLD["Arquitectura Antigua (Bridge)"]
    direction TB
    J1["Hilo JS<br/>Lógica React"] -->|"Serializar JSON"| BR["Bridge<br/>Cola asíncrona ⚠"]
    BR -->|"Deserializar JSON"| N1["Hilo Nativo<br/>UIKit / Android View"]
    SH["Hilo Shadow<br/>Layout Yoga"] --> N1
  end
  subgraph NEW["Nueva Arquitectura (JSI)"]
    direction TB
    HM["Motor Hermes<br/>Ejecución bytecode"] -->|"Referencia C++ directa"| JSI["JSI<br/>Host Objects"]
    JSI --> FAB["Fabric<br/>Renderizador síncrono"]
    JSI --> TM["TurboModules<br/>Inicialización perezosa"]
    FAB --> N2["Capa Nativa"]
    TM --> N2
  end

Problemas de la Arquitectura Antigua (Bridge)

El React Native legacy hacía comunicar los hilos JS y nativo a través de un único cuello de botella asíncrono llamado Bridge. Cada intercambio debía serializarse como JSON, enviarse y deserializarse — haciendo que operaciones frecuentes como animaciones y gestos fueran propensas a problemas de rendimiento.

Innovaciones de la Nueva Arquitectura (JSI + Hermes)

JSI (JavaScript Interface) es una capa de enlace delgada en C++ que permite al motor JS referenciar directa y síncronamente objetos nativos. Esto habilitó:

  • Fabric — Un nuevo renderizador que construye y actualiza árboles UI síncronamente
  • TurboModules — Inicialización perezosa que aplaza la carga de módulos nativos hasta que se necesiten
  • Hermes — El motor de Facebook que precompila JS en bytecode, reduciendo drásticamente el tiempo de inicio

3. Codegen: La Clave para la Comunicación Nativa Segura en Tipos

Codegen es el mecanismo más ignorado en la nueva arquitectura. Mientras JSI y TurboModules hacen la comunicación posible, Codegen garantiza que es correcta en tiempo de compilación.

flowchart TD
  SPEC["Archivo JS Spec<br/>Definiciones de tipos en TypeScript / Flow"]

  subgraph BUILD["Tiempo de Compilación"]
    CG["Codegen<br/>Se ejecuta durante pod install / Gradle"]
  end

  subgraph OUT["Artefactos Generados"]
    CPP["Clases C++ Abstractas<br/>Interfaces base TurboModule"]
    DESC["Component Descriptor<br/>Para componentes nativos Fabric"]
    JSTYPE["Definiciones de Tipos JS<br/>Equivalente a .d.ts"]
  end

  subgraph NATIVE["Implementación Nativa"]
    IOS["iOS<br/>Implementado en Swift / Obj-C"]
    AND["Android<br/>Implementado en Kotlin / Java"]
  end

  RUNTIME["Vía JSI<br/>Seguro en tipos, llamadas síncronas"]

  SPEC --> CG
  CG --> CPP & DESC & JSTYPE
  CPP --> IOS & AND
  DESC --> IOS & AND
  IOS --> RUNTIME
  AND --> RUNTIME

En la arquitectura antigua, la interfaz entre JS y nativo era esencialmente un "acuerdo de caballeros". Llamar a NativeModules.MyModule.doSomething(42) desde JS no fallaría hasta un crash en tiempo de ejecución si el lado nativo esperaba un String.

Codegen resuelve esto en tiempo de compilación. Los desarrolladores escriben una Spec (especificación) en TypeScript o Flow describiendo qué tipos acepta un módulo nativo. Durante pod install (iOS) o la compilación Gradle (Android), Codegen genera automáticamente clases C++ abstractas, andamiaje nativo para iOS y Android y definiciones de tipos JS.


4. El Pipeline del Metro Bundler

Metro es el bundler JavaScript dedicado de React Native. Cumple el mismo rol que webpack, pero está optimizado para el desarrollo móvil.

flowchart LR
  SRC[".js / .ts / .tsx<br/>Archivos fuente"]
  RES["Resolución<br/>Resolución de rutas de import"]
  TRA["Transformación<br/>Transforms ES via Babel"]
  SER["Serialización<br/>Generación del bundle"]
  DEV["Entrega al dispositivo<br/>HTTP :8081"]
  HMR["HMR<br/>Reenviar solo módulos cambiados"]
  CACHE["Caché FS<br/>Más rápido desde el 2do arranque"]

  SRC --> RES --> TRA --> SER --> DEV
  TRA -.->|"Detección de cambio de archivo"| HMR
  HMR -.->|"Reflejar sin perder state"| DEV
  TRA <-.->|"Cachear resultados transformados"| CACHE

El pipeline de Metro tiene tres fases:

  • Resolución — Resolución de módulos al estilo Node.js con selección automática de archivos específicos por plataforma (foo.ios.ts / foo.android.ts)
  • Transformación — Babel convierte JSX, TypeScript y ES moderno; los resultados se cachean para inicios más rápidos
  • Serialización — Genera el bundle final y lo entrega al dispositivo mediante un servidor HTTP

Notablemente, el HMR (Hot Module Replacement) solo actualiza el grafo de módulos cambiados, reflejando cambios de código instantáneamente mientras preserva el estado de los componentes React. Las compilaciones de producción combinan --minify y la compilación Hermes en bytecode para reducir tanto el tamaño del archivo como el tiempo de inicio.


5. La Estructura por Capas de Expo y las Herramientas de Desarrollo

flowchart TD
  APP["Tu App<br/>App.tsx / screens / hooks"]
  SDK["Expo SDK<br/>expo-camera / expo-location / expo-router …"]
  EMC["expo-modules-core<br/>DSL con seguridad de tipos en Swift / Kotlin"]
  RN["React Native Core<br/>View / Text / Animated / StyleSheet"]
  IOS["iOS<br/>UIKit / AVFoundation / CoreLocation"]
  AND["Android<br/>View / Camera2 / LocationManager"]
  GO["Expo Go<br/>Lanzar instantáneamente via QR"]
  EASB["EAS Build<br/>Generar .ipa / .apk en la nube"]
  EASU["EAS Update<br/>Entregar solo el JS bundle via OTA"]
  STORE["App Store / Google Play"]

  APP --> SDK
  EMC --> SDK
  SDK --> RN
  RN --> IOS & AND
  GO -->|"Alojar en desarrollo"| APP
  EASB --> STORE
  EASU -->|"Sin revisión App Store"| APP

El SDK de Expo proporciona funcionalidades nativas — cámara, ubicación, notificaciones, sistema de archivos — como paquetes versionados. Sin necesidad de escribir código nativo propio — ese es su mayor valor.

expo-modules-core es un sistema más nuevo que permite el desarrollo declarativo de módulos nativos con DSLs de Swift y Kotlin, integrado con Codegen. Es más seguro en tipos y menos verboso que el antiguo bridge NativeModules, y casi todos los SDKs de terceros de Expo lo usan.

EAS (Expo Application Services) es un término general para Build, Update, Submit, Workflows y más. Este artículo se centra en EAS Build y EAS Update. EAS Build compila binarios nativos en la nube, habilitando builds de iOS sin Mac. EAS Update entrega bundles JS y assets via OTA, permitiendo entrega rápida de cambios que no tocan código nativo.


6. Áreas que Expo no Cubre y la Elección del Workflow

Principales áreas que el SDK de Expo no cubre:

  • Bluetooth LE / NFC — Comunicación con periféricos específicos de plataforma
  • SDKs personalizados de pago/biométrica — Integración directa de .framework / .aar de proveedores
  • Procesamiento de audio/video en tiempo real — WebRTC de bajo nivel u operaciones con codecs personalizados
  • Bibliotecas nativas internas — Assets Swift / Kotlin extraídos de apps existentes
  • Vistas de cámara personalizadas — Renderizado personalizado con OpenGL / Metal
flowchart TD
  START["¿Qué funcionalidad nativa necesitas?"]

  START --> Q1{"¿Cubierta por SDK Expo<br/>o biblioteca pública?"}
  Q1 -->|"Sí"| MANAGED["Managed Workflow<br/>Sin código nativo necesario<br/>Solo npx expo start"]
  Q1 -->|"No"| Q2{"¿Solo cambios de configuración?<br/>(Adiciones Info.plist / AndroidManifest)"}
  Q2 -->|"Sí"| PLUGIN["Crear Config Plugin<br/>Agregar a app.json<br/>Aplicado automaticamente por prebuild"]
  Q2 -->|"No"| Q3{"¿Cuánto código nativo personalizado?"}
  Q3 -->|"Ligero<br/>(envolver SDK existente)"| BARE["Bare Workflow<br/>npx expo prebuild<br/>→ Gestionar ios/ android/ directamente"]
  Q3 -->|"Completo<br/>(módulo / UI personalizado)"| MODULE{"¿Qué se construye?"}
  MODULE -->|"Lógica<br/>Bluetooth / pago / cripto"| TM["TurboModule<br/>+ Codegen"]
  MODULE -->|"Componente UI<br/>Vista cámara personalizada, etc."| FAB["Fabric Component<br/>+ Codegen"]
  BARE --> TM & FAB
  PLUGIN --> BARE

Los cambios de configuración menores pueden manejarse con Config Plugins. Pero si se necesita escribir código, generar los directorios ios/ y android/ con npx expo prebuild y pasar al Bare Workflow es el punto de partida práctico.


7. Arquitectura Completa: JSI + Codegen + Implementación Nativa

flowchart TB
  subgraph JS["Capa JS (Hermes)"]
    APP["Código App / Componentes React"]
    SPEC["JS Spec<br/>(TypeScript + TurboModuleRegistry)"]
  end

  subgraph CODEGEN["Codegen (Auto-generado en tiempo de compilación)"]
    GEN_CPP["Clases C++ Abstractas<br/>TurboModule / ComponentDescriptor"]
    GEN_SHADOW["Definiciones ShadowNode / Props"]
  end

  subgraph JSI_LAYER["Capa JSI (C++)"]
    JSI["JSI Host Object"]
    FABRIC["Fabric Renderer"]
  end

  subgraph NATIVE_IOS["Implementación Nativa iOS"]
    SWIFT["Implementación Swift / Obj-C<br/>(Hereda clase C++ abstracta)"]
    SDK_IOS["SDK Interno / .framework Externo<br/>ej: CoreBluetooth / Stripe"]
    UIVIEW["Subclase UIView<br/>(Componente UI Personalizado)"]
  end

  subgraph NATIVE_AND["Implementación Nativa Android"]
    KOTLIN["Implementación Kotlin / Java<br/>(Hereda clase C++ abstracta)"]
    SDK_AND["SDK Interno / .aar Externo<br/>ej: BluetoothGatt / Stripe"]
    ANDROIDVIEW["Subclase View<br/>(Componente UI Personalizado)"]
  end

  SPEC -->|"Leer definiciones de tipos"| CODEGEN
  GEN_CPP --> JSI
  GEN_SHADOW --> FABRIC
  APP -->|"Llamadas síncronas / asíncronas"| JSI
  JSI --> SWIFT & KOTLIN
  FABRIC --> UIVIEW & ANDROIDVIEW
  SWIFT --> SDK_IOS
  KOTLIN --> SDK_AND

La clave es la dependencia unidireccional: Spec → Codegen → Implementación Nativa. Escribe definiciones de tipos en el lado JS, y Codegen genera clases C++ abstractas en tiempo de compilación. Las implementaciones Swift / Kotlin solo necesitan heredarlas — la consistencia de tipos la garantiza el compilador, eliminando el "acuerdo de caballeros" como fuente de crashes en tiempo de ejecución.


8. Flujo de Implementación TurboModule (Ejemplo Bluetooth)

Nota: El código siguiente es un ejemplo simplificado basado en las guías oficiales de React Native para TurboModule / Codegen / Custom Events. No son citas directas de los ejemplos oficiales, sino una reconstrucción usando Bluetooth como ejemplo.

① Escribir la JS Spec

// src/specs/NativeBluetoothModule.ts
import type { TurboModule, CodegenTypes } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export type ScanResult = {
  id: string;
  name: string;
  rssi: number;
};

export interface Spec extends TurboModule {
  startScan(serviceUUIDs: string[]): void;
  stopScan(): void;
  connect(deviceId: string): Promise<boolean>;
  readonly onDeviceFound: CodegenTypes.EventEmitter<ScanResult>;
}

export default TurboModuleRegistry.getEnforcing<Spec>('NativeBluetoothModule');

② Lo que Codegen Genera (Automáticamente)

flowchart LR
  SPEC["NativeBluetoothModule.ts<br/>(Escrito por el desarrollador)"]

  subgraph AUTO["Código auto-generado"]
    CPP["Código C++ glue<br/>Salida JSI / Codegen"]
    IOS_H["Código iOS glue<br/>Salida Obj-C++ / header"]
    AND_J["Código Android glue<br/>Salida JNI / clase Spec"]
  end

  subgraph IMPL["Implementado por el desarrollador"]
    SWIFT_IMPL["NativeBluetoothModule.swift<br/>Implementa Spec generada, llama CoreBluetooth"]
    KOTLIN_IMPL["NativeBluetoothModule.kt<br/>Implementa Spec generada, llama BluetoothGatt"]
  end

  SPEC --> CPP
  SPEC --> IOS_H
  SPEC --> AND_J
  CPP --> SWIFT_IMPL & KOTLIN_IMPL
  IOS_H --> SWIFT_IMPL
  AND_J --> KOTLIN_IMPL

③ Implementación iOS (Swift)

// ios/NativeBluetoothModule.swift
import CoreBluetooth

@objc(NativeBluetoothModule)
class NativeBluetoothModule: NativeBluetoothModuleSpec, CBCentralManagerDelegate {
  private var centralManager: CBCentralManager!

  override func startScan(_ serviceUUIDs: [String]) {
    let uuids = serviceUUIDs.map { CBUUID(string: $0) }
    centralManager.scanForPeripherals(withServices: uuids)
  }

  func centralManager(_ central: CBCentralManager,
                      didDiscover peripheral: CBPeripheral,
                      advertisementData: [String: Any], rssi: NSNumber) {
    emitOnDeviceFound([
      "id": peripheral.identifier.uuidString,
      "name": peripheral.name ?? "",
      "rssi": rssi
    ])
  }
}

④ Implementación Android (Kotlin)

// android/src/main/java/NativeBluetoothModule.kt
class NativeBluetoothModule(reactContext: ReactApplicationContext)
    : NativeBluetoothModuleSpec(reactContext), BluetoothGatt.Callback {

  private val adapter = BluetoothAdapter.getDefaultAdapter()

  override fun startScan(serviceUUIDs: ReadableArray) {
    val filters = serviceUUIDs.toArrayList().map { uuid ->
      ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString(uuid as String)).build()
    }
    adapter.bluetoothLeScanner.startScan(filters, ScanSettings.Builder().build(), scanCallback)
  }

  private val scanCallback = object : ScanCallback() {
    override fun onScanResult(callbackType: Int, result: ScanResult) {
      val params = Arguments.createMap().apply {
        putString("id", result.device.address)
        putString("name", result.device.name ?: "")
        putInt("rssi", result.rssi)
      }
      emitOnDeviceFound(params)
    }
  }
}

9. Flujo de Implementación Fabric Component (Ejemplo Vista Cámara Personalizada)

Si TurboModule maneja la lógica, Fabric Component sirve para incrustar UI nativa en JSX. Necesario para renderizado personalizado con OpenGL / Metal, o reutilizar vistas nativas existentes.

Nota: Lo siguiente también es un ejemplo mínimo para la comprensión. Las rutas de import de Codegen y nombres de tipos varían según la versión de React Native, siempre consulta la documentación oficial de tu versión al implementar.

// src/specs/NativeCameraViewSpec.ts
import type { ViewProps } from 'react-native';
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
import type { DirectEventHandler, Float } from 'react-native/Libraries/Types/CodegenTypes';

type FrameCapturedEvent = Readonly<{
  uri: string;
}>;

interface NativeProps extends ViewProps {
  zoom?: Float;          // Codegen mapea Float → CGFloat / float
  torchEnabled?: boolean;
  onFrameCaptured?: DirectEventHandler<FrameCapturedEvent>;
}

export default codegenNativeComponent<NativeProps>('CameraView');

Codegen genera definiciones C++ para ShadowNode, ComponentDescriptor y Props desde esta Spec. En iOS, implementar un CameraViewComponentView que herede de RCTViewComponentView; en Android, heredar de ReactViewGroup. Desde el lado JS, es tan simple como <CameraView zoom={2.0} torchEnabled />.


10. Integración de SDKs Internos / .framework y .aar Externos

iOS: Referencia Local via CocoaPods

# ios/Podfile
pod 'CompanyPaymentSDK', :path => '../vendor/ios/CompanyPaymentSDK'
# o para xcframework
pod 'CompanyPaymentSDK', :podspec => '../vendor/ios/CompanyPaymentSDK.podspec'

Tras añadirlo al Podfile y ejecutar pod install, se puede usar como una clase Swift normal con import CompanyPaymentSDK. Llamarlo directamente desde la implementación TurboModule.

Android: Maven Local / Referencia .aar

// android/app/build.gradle
dependencies {
  implementation files('../vendor/android/company-payment-sdk.aar')
  // o un repositorio Maven interno
  implementation 'com.company:payment-sdk:1.4.0'
}

11. El Bucle de Desarrollo: Del Inicio a la Verificación

flowchart TD
  INIT["npx create-expo-app MyApp<br/>--template blank-typescript"]

  subgraph START["npx expo start"]
    METRO["Metro Bundler arranca<br/>puerto 8081"]
    QR["Código QR + atajos<br/>i = iOS  a = Android  w = Web"]
  end

  subgraph DEVICE["Elegir objetivo"]
    SIM["iOS Simulator<br/>Incluido con Xcode (solo Mac)"]
    EMU["Android Emulator<br/>Android Studio AVD"]
    GO["Dispositivo físico + Expo Go<br/>Via LAN o Tunnel"]
  end

  subgraph NATIVE_BUILD["Solo la primera vez: Build Nativo"]
    POD["pod install<br/>iOS CocoaPods"]
    GRADLE["Gradle build<br/>Android"]
    CODEGEN_RUN["Codegen se ejecuta<br/>Spec → Andamiaje nativo"]
  end

  BUNDLE["Transferir JS bundle al dispositivo<br/>via servidor HTTP de Metro"]
  RENDER["La pantalla se muestra"]

  INIT --> START
  METRO --> QR
  QR --> SIM & EMU & GO
  SIM --> NATIVE_BUILD
  EMU --> NATIVE_BUILD
  NATIVE_BUILD --> BUNDLE
  GO --> BUNDLE
  BUNDLE --> RENDER

Ejecuta npx expo start para arrancar Metro, y aparece un código QR con atajos de teclado en el terminal.

  • Tecla i → iOS Simulator (funciona en Mac con Xcode instalado)
  • Tecla a → Android Emulator (dispositivo virtual creado en Android Studio AVD Manager)
  • Para dispositivos físicos, escanear el QR en Expo Go — se conecta casi instantáneamente en LAN

Al desencadenar explícitamente un build nativo con npx expo run:ios o npx expo run:android, pod install (iOS) o Gradle (Android) se ejecutan la primera vez y Codegen genera el andamiaje nativo. Las ejecuciones posteriores solo transfieren diferencias del JS bundle y son mucho más rápidas.


12. El Bucle de Cambios en Bare Workflow

flowchart TD
  subgraph CHANGE["Ramificación por tipo de cambio"]
    JS_ONLY["Solo cambios JS<br/>Componentes / lógica / estilos"]
    NATIVE_CHANGE["Cambios de código nativo<br/>Swift / Kotlin / Spec / Podfile"]
  end

  subgraph JS_LOOP["Bucle de Cambio JS (Rápido)"]
    METRO_HMR["Metro HMR<br/>Transferir bundle diferencial<br/>~Cientos de ms"]
    SIM_JS["Visible de inmediato en emulador<br/>Estado de la app preservado"]
  end

  subgraph NATIVE_LOOP["Bucle de Cambio Nativo (Lento)"]
    CODEGEN_RUN["Re-ejecutar Codegen<br/>pod install (iOS)<br/>Gradle sync (Android)"]
    REBUILD["Rebuild nativo<br/>npx expo run:ios<br/>npx expo run:android"]
    SIM_NATIVE["Verificar en emulador / dispositivo"]
  end

  JS_ONLY --> METRO_HMR --> SIM_JS
  NATIVE_CHANGE --> CODEGEN_RUN --> REBUILD --> SIM_NATIVE

  SIM_JS -->|"Sin problemas"| DONE["Verificación OK"]
  SIM_NATIVE -->|"Sin problemas"| DONE
  SIM_JS -->|"Bug encontrado"| DEBUG_JS["Hermes Debugger<br/>React DevTools"]
  SIM_NATIVE -->|"Crash nativo"| DEBUG_NATIVE["Xcode / Android Studio<br/>Depurador nativo"]
  DEBUG_JS --> JS_ONLY
  DEBUG_NATIVE --> NATIVE_CHANGE

Los cambios solo de JS son manejados por Metro HMR en cientos de milisegundos, preservando el estado.

Los cambios de código nativo requieren un rebuild. El primer build de iOS toma 2–5 minutos, los builds diferenciales unos 30 segundos a un minuto. Android con caché de Gradle es similar; el primer build toma más tiempo.

Esta asimetría es clave para la eficiencia de desarrollo en Bare Workflow: fijar temprano el límite nativo (Spec) y empujar la mayor parte de la implementación al lado JS para reducir la frecuencia de builds nativos pesados.


13. Depuración: Investigación a Través del Límite JS/Nativo

flowchart TD
  SYMPTOM["Observar síntomas"]

  SYMPTOM --> Q1{"Tipo de crash"}

  Q1 -->|"Error JS<br/>Pantalla roja LogBox"| JS_ERR["Investigar con Hermes Debugger<br/>Breakpoints, stack trace<br/>Números de línea TS via Source Map"]
  Q1 -->|"Crash nativo<br/>App congela / crashea"| NAT_ERR["Revisar logs de crash"]
  Q1 -->|"Funciona pero<br/>resultado incorrecto"| BORDER["Sospechar del límite JS↔Nativo"]

  NAT_ERR --> IOS_CRASH["iOS: Xcode<br/>Debug Navigator<br/>+ adjuntar lldb"]
  NAT_ERR --> AND_CRASH["Android: Android Studio<br/>Stack trace Logcat<br/>+ Native Debugger"]

  BORDER --> LOG_IOS["iOS: NSLog / os_log<br/>npx react-native log-ios"]
  BORDER --> LOG_AND["Android: Log.d<br/>npx react-native log-android"]
  BORDER --> JS_LOG["Lado JS: console.log<br/>Verificar valores de retorno TurboModule"]

  IOS_CRASH --> INSTRUMENTS["Instruments<br/>Time Profiler / Allocations<br/>Identificar fugas de memoria / CPU"]
  AND_CRASH --> PROFILER["Android Studio<br/>Memory / CPU Profiler"]

  JS_ERR --> FIXED["Corregir → Visible de inmediato via Metro HMR"]
  INSTRUMENTS --> FIXED2["Corregir → Verificar con npx expo run:ios"]
  PROFILER --> FIXED3["Corregir → Verificar con npx expo run:android"]
  LOG_IOS & LOG_AND & JS_LOG --> BORDER2["Identificar causa raíz<br/>→ Corregir Spec / implementación"]

Errores JS: Hermes Debugger

Cmd+D → "Open Debugger" abre Chrome DevTools. Metro proporciona Source Maps, permitiendo colocar breakpoints en los números de línea TypeScript originales — no en el código bundleado. Las excepciones en llamadas TurboModule muestran el stack completo de vuelta al lado JS.

Crashes Nativos: Adjuntar el Depurador de Xcode / Android Studio

# iOS: Adjuntar lldb al simulador en ejecución
npx expo run:ios --configuration Debug
# → Xcode se abre automáticamente y adjunta el depurador

# Android
npx expo run:android
# → "Run > Attach Debugger to Android Process"

Para crashes que solo se reproducen en dispositivos físicos (especialmente Bluetooth, cámara, sensores), el depurador puede adjuntarse de la misma forma a un dispositivo conectado por USB.

Investigación de Límite: Encerrar con Logs

Cuando los datos se corrompen en el límite JS↔Nativo, encerrarlos con logs de ambos lados es el enfoque más rápido.

// Lado Swift
os_log("BluetoothModule.connect called: %{public}@", deviceId)
// → Filtrar con npx react-native log-ios
// Lado Kotlin
Log.d("BluetoothModule", "connect called: $deviceId")
// → Filtrar con npx react-native log-android
// Lado JS
const result = await BluetoothModule.connect(deviceId);
console.log('connect result:', result); // Salida en Metro

React DevTools

npx react-devtools
# → Espera conexión del dispositivo en el puerto 8097

Visualizar el árbol de componentes e inspeccionar (y modificar) props y state en tiempo real.


14. Simuladores vs. Dispositivos Físicos

Qué se prueba iOS Simulator Android Emulator Dispositivo Físico
UI / Layout Suficiente Suficiente Verificación final
Lógica JS / API Suficiente Suficiente No necesario
Bluetooth / NFC Imposible Imposible Requerido
Cámara / Micrófono Parcial Imposible Requerido
Notificaciones push Parcial Parcial Recomendado
Rendimiento Solo referencia Solo referencia Requerido
Autenticación biométrica Sim Face ID disponible Sim huella disponible Recomendado

Para el desarrollo con módulos nativos, un enfoque de dos etapas funciona bien: consolidar el comportamiento JS en emuladores, verificar las funcionalidades nativas en dispositivos físicos. Especialmente para Bluetooth y NFC — ya que los emuladores no pueden probarlos — el número de dispositivos y la cobertura de versiones OS impacta directamente la calidad.


Resumen

El Stack Tecnológico Completo

Capa Tecnología Rol
Bundler Metro Transpilación de fuentes, HMR, ramificación de plataforma
Motor JS Hermes Ejecución bytecode para inicio rápido
Bridge JS↔Nativo JSI Enlace síncrono via C++
Generación de Código Codegen Auto-generación de andamiaje nativo con seguridad de tipos desde la Spec
Renderizador UI Fabric Renderizado UI síncrono sobre JSI
APIs Nativas TurboModules Módulos con inicialización perezosa que heredan clases generadas por Codegen
Infraestructura Dev Expo / EAS SDK, builds en la nube, actualizaciones OTA

Flujo Completo para Implementación Nativa

① Escribir la JS Spec (definiciones de tipos en TypeScript)
       ↓
② pod install / gradle build → Codegen auto-genera andamiaje C++
       ↓
③ Implementar en Swift / Kotlin (llamar SDK interno / bibliotecas externas)
       ↓
④ npx expo run:ios / run:android → Build nativo & verificación en emulador
       ↓
⑤ Escribir código de llamada JS → Iteración rápida via Metro HMR
       ↓
⑥ Errores JS → Hermes Debugger
   Crashes nativos → Depurador Xcode / Android Studio
   Discrepancias en límite → Encerrar con log-ios / log-android + console.log
       ↓
⑦ Verificar Bluetooth / cámara / sensores en dispositivos físicos
       ↓
⑧ EAS Build genera binarios listos para la tienda

La transición de React Native de Bridge a JSI + Codegen lo transformó de "funciona pero frágil" a "con seguridad de tipos y alto rendimiento". Salir de Expo trae costos — builds pesados y escribir la misma lógica en dos lenguajes — pero Codegen hace de la Spec la única fuente de verdad, eliminando el mayor riesgo: los crashes en tiempo de ejecución por incompatibilidades de tipos, desde la fase de diseño.


URLs de Referencia

Nota: Este artículo resume y reconstruye información de las siguientes fuentes. Los ejemplos y explicaciones han sido simplificados para mayor claridad y no son citas directas. Siempre consultar la documentación oficial de la versión que se usa al implementar.

URLs Referenciadas

React Native Oficial

Expo Oficial