React Native
Nuestro SDK de Workflows para React Native permite una experiencia rápida y fluida para tus usuarios, aprovechando las capacidades nativas de los dispositivos. Este SDK se instala como un paquete npm y te permite ejecutar workflows, obtener información detallada del proceso y recibir eventos en tiempo real durante su ejecución.
Requisitos
Para poder utilizar nuestro SDK de React Native, debes cumplir con los siguientes requisitos:
- React Native 0.70+
- Node.js 18+
- Contar con un Workflow creado a través de nuestra API de Workflows.
- Acceso al registro privado de npm (se te proporcionará un archivo
.npmrc).
Instalación
Configuración del archivo .npmrc
En la raíz del proyecto React Native, agrega el archivo .npmrc para autorizar la instalación del paquete. Este archivo te será proporcionado por tu administrador de cuenta.
Instalación del paquete
Ejecuta el siguiente comando en la raíz del proyecto:
npm install @rem-tools/workflows-react-native@0.5.0
Nota: Verifica la versión actual en el registro de npm.
Configuración para Expo
Si utilizas Expo, puedes omitir la configuración nativa usando el comando:
npx expo prebuild --clean
En tu archivo app.json o app.config.js, agrega la configuración del plugin:
{
"expo": {
"plugins": [
[
"@rem-tools/workflows-react-native",
{
"jsonKeyPath": "/ruta/a/tu/archivo-service-account.json",
"cameraDescription": "Se requiere acceso a la cámara para verificación de identidad",
"microphoneDescription": "Se requiere acceso al micrófono para grabar audio",
"locationDescription": "Se requiere acceso a la ubicación por motivos de seguridad"
}
]
]
}
}
Opciones de Configuración del Plugin
| Parámetro | Requerido | Descripción |
|---|---|---|
jsonKeyPath | Sí | Ruta absoluta o relativa al archivo JSON de la cuenta de servicio de Google Cloud. Requerido para Android para autenticarse con el repositorio Maven privado. |
cameraDescription | No | Descripción personalizada para el permiso de cámara (iOS). |
microphoneDescription | No | Descripción personalizada para el permiso de micrófono (iOS). |
locationDescription | No | Descripción personalizada para el permiso de ubicación (iOS). |
Ejecutar la aplicación
Para Android (dispositivo físico):
Asegúrate de que el proyecto Android haya completado el build de Gradle:
npm run android --device
Para iOS (dispositivo físico):
Asegúrate de haber ejecutado pod install en el directorio ios:
npx expo run:ios --device
Configuración Específica para Android
Requisitos de SDK de Android
- Android API 29 - 36
- Android Studio Hedgehog+
Configuración de Repositorios
En el archivo android/build.gradle, declara de dónde se obtendrán las dependencias:
allprojects {
repositories {
maven {
url "https://us-central1-maven.pkg.dev/gendra-services/workflows-mobile"
credentials {
username = "_json_key"
password = file("tu_archivo_credenciales.json").text
}
}
}
}
El archivo de service account (JSON) es proporcionado por REM. Contacta a tu administrador de cuenta para obtenerlo.
Configuración de Release Build
En el archivo android/app/build.gradle, agrega a nivel de android los siguientes ajustes que permiten que Workflows pueda acceder a modelos, assets, y evitar errores en compilación de release:
android {
// ...
buildTypes {
release {
shrinkResources false
}
}
aaptOptions {
noCompress "tflite", "task"
}
sourceSets {
main {
assets.srcDirs += 'src/main/assets'
}
}
}
No excluyas librerías .so requeridas por el SDK usando packagingOptions o packaging.jniLibs.
Por ejemplo, NO debes usar:
packagingOptions {
exclude 'lib/arm64-v8a/libPhoenixAndroid.so'
}
El SDK depende de librerías nativas como:
lib/arm64-v8a/libPhoenixAndroid.so
Si se excluyen, la aplicación puede compilar pero fallará en ejecución, especialmente en la inicialización de biometría.
Configuración del AndroidManifest
Agrega esta línea para resolver conflictos del AndroidManifest cuando una librería (como Workflows) define los mismos atributos:
<application
...
tools:replace="android:theme, android:usesCleartextTraffic">
...
</application>
Reglas para ProGuard
Agrega las siguientes reglas al archivo proguard-rules.pro. Son necesarias para evitar la minificación de clases usadas por Workflows:
#Rules for the Workflows Flutter
-keep class tools.rem.workflows_flutter_plugin.** { *; }
-keep class tools.rem.workflows_flutter_plugin.Models.Step.Step { *; }
-keep class tools.rem.workflows_flutter_plugin.Models.Workflow.Workflow { *; }
-keep class tools.rem.workflows_flutter_plugin.Models.WorkflowError { *; }
-keep class tools.rem.workflows_flutter_plugin.WorkflowsFlutterPlugin { *; }
-keepclassmembers class tools.rem.workflows_flutter_plugin.Models.Workflow.Workflow {
public <init>(...);
}
-keep class tools.rem.workflows_flutter_plugin.Models.ConfigFacetec { *; }
-keepclassmembers class tools.rem.workflows_flutter_plugin.Models.ConfigFacetec {
public <init>(...);
}
#Keep the mediapipe classes
-keep public class com.google.mediapipe.framework.Graph.** { *; }
-keep interface com.google.common.** { *; }
-keep class * extends com.google.common.flogger.** {*;}
-keep class com.google.common.** { *; }
-keep class com.google.common.flogger.** { *; }
-keep class com.google.mediapipe.tasks.** { *; }
-keep class com.google.mediapipe.tasks.vision.** { *; }
-keep class com.google.mediapipe.tasks.core.** { *; }
-keep class com.google.mediapipe.framework.ProtoUtil$* { *; }
-keep class com.google.mediapipe.framework.** { *; }
-keep class com.google.mediapipe.framework.image.** { *; }
-keep class com.google.mediapipe.** { *; }
-keep class com.google.mediapipe.proto.** { *; }
-keep class javax.lang.model.** { *; }
-keep class com.google.mediapipe.solutioncore.** { *; }
-keep class com.google.protobuf.** { *; }
-keep class org.tensorflow.lite.** { *; }
-dontwarn com.google.mediapipe.proto.CalculatorProfileProto$CalculatorProfile
-dontwarn com.google.mediapipe.proto.GraphTemplateProto$CalculatorGraphTemplate
-dontwarn org.tensorflow.lite.**
-dontwarn javax.annotation.**
-dontwarn javax.lang.model.**
-keepclassmembers class * extends com.google.protobuf.GeneratedMessageLite.** { *; }
-keepclassmembers class com.google.mediapipe.**$$ExternalSyntheticLambda* { *; }
-keepattributes InnerClasses,EnclosingMethod,Signature,*Annotation*
#Keep the okhttp3 classes
-keep class okhttp3.** { *; }
-keep class okio.** { *; }
#Keep the gson classes
-keep class com.google.gson.** { *; }
#Keep the facetec classes
-dontwarn javax.annotation.Nullable
-dontwarn com.facetec.sdk.**
-keep,includecode,includedescriptorclasses class com.facetec.sdk.** { *; }
#Keep the cameraview classes
-keep class com.otaliastudios.cameraview.** { *; }
-dontwarn com.otaliastudios.cameraview.**
Recursos Personalizados
Para usar recursos personalizados, agrega los assets en la carpeta res según su tipo:
| Carpeta | Tipo de Recurso | Formatos |
|---|---|---|
drawable | Imágenes | .png, .jpeg |
font | Tipografías | .ttf, .otf |
raw | Imágenes vectoriales | .svg |
xml | Textos localizados | .xml |
Configuración Específica para iOS
Requisitos de iOS
- iOS 15.1 - 26.0
- Xcode 14.0+
Configuración del Podfile
En el archivo ios/Podfile, establece la versión mínima en 15.1 y agrega el pod de WorkflowController:
platform :ios, '15.1'
target 'TuApp' do
# Otras configuraciones y pods
pod 'WorkflowControllerDebug',
:podspec => 'https://storage.googleapis.com/workflows-mobile-sdk-artifacts/WorkflowiOS/WorkflowController/WorkflowsControllerDebug.podspec?cache=1225',
:configurations => ['Debug']
pod 'WorkflowControllerRelease',
:podspec => 'https://storage.googleapis.com/workflows-mobile-sdk-artifacts/WorkflowiOS/WorkflowController/WorkflowsControllerRelease.podspec?cache=1225',
:configurations => ['Release']
end
Nota: El target
WorkflowsReactNativees generado automáticamente por CocoaPods al instalar este paquete. No es necesario modificar este nombre.
Agrega el siguiente bloque post_install en tu Podfile:
post_install do |installer|
media_pipe_source = if File.exist?("Pods/WorkflowControllerDebug")
"WorkflowControllerDebug"
elsif File.exist?("Pods/WorkflowControllerRelease")
"WorkflowControllerRelease"
else
nil
end
system("mv Pods/#{media_pipe_source}/MediaPipeTasks*.xcframework Pods/") if media_pipe_source
installer.pods_project.targets.each do |target|
if target.name == 'WorkflowsReactNative'
puts "[WorkflowSDK] MATCHED Pod Target: WorkflowsReactNative"
group = target.project.main_group.find_subpath("Pods/#{media_pipe_source}", true) if media_pipe_source
%w(MediaPipeTasksVision.xcframework MediaPipeTasksCommon.xcframework).each do |fw|
file_path = File.join(Pod::Config.instance.installation_root, "Pods", media_pipe_source, fw)
puts "[WorkflowSDK] Adding #{fw} to #{target.name}"
ref = group.new_file(file_path)
ref.source_tree = 'SOURCE_ROOT'
target.frameworks_build_phase.add_file_reference(ref, true)
build_files = target.frameworks_build_phase.files.select { |f| f.file_ref == ref }
build_files.each { |bf| bf.settings = { 'ATTRIBUTES' => ['Required'] } }
end
end
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.1'
config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = "arm64"
config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
end
end
installer.pods_project.save
end
Instalación de Dependencias
Desde la carpeta ios de tu proyecto, ejecuta:
pod install
Importante: Recomendamos limpiar las dependencias de CocoaPods antes de volver a instalar.
Desde la carpeta ios, ejecuta:
rm -rf Pods
rm Podfile.lock
pod install
Esto asegura que las dependencias nativas se reinstalen correctamente y evita conflictos con versiones previamente instaladas.
Permisos en Info.plist
Agrega los permisos necesarios al archivo Info.plist o en el target properties seleccionando la pestaña Info:
<key>NSCameraUsageDescription</key>
<string>3D Liveness Detection by FaceTec.</string>
<key>NSMicrophoneUsageDescription</key>
<string>We need access to your microphone to record audio.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>The example App requires access to the devices location.</string>
Recursos Personalizados
Para usar recursos personalizados, agrégalos directamente a la carpeta del proyecto:
| Tipo de Recurso | Formato | Ubicación |
|---|---|---|
| Textos | .strings | Carpeta del proyecto |
| Imágenes SVG | .svg | Carpeta del proyecto |
| Tipografías | .otf, .ttf | Carpeta del proyecto |
| Imágenes PNG/JPG | .png, .jpg | Asset Catalog |
Uso del SDK
Importación
Importa los componentes necesarios del SDK:
import type { WorkflowCustomization } from '@rem-tools/workflows-react-native';
import {
closeWorkflow,
useWorkflow,
useWorkflowEvents,
verifyWorkflowsLicense
} from '@rem-tools/workflows-react-native';
Verificación de Licencia
Antes de iniciar un workflow, debes verificar la licencia del SDK:
verifyWorkflowsLicense({
cert: "tu_certificado", // Base64-encoded PFX
passphrase: "tu_passphrase" // Base64-PFX passphrase
});
Las credenciales de licencia (cert y passphrase) serán compartidas de forma privada por tu administrador de cuenta.
Inicialización del Workflow
Utiliza el hook useWorkflow para configurar y lanzar un workflow:
const launchWorkflow = useWorkflow({
baseUrl: 'https://api.rem.tools', // Your Workflows backend URL
apiKey: 'tu_api_key', // Your API key
workflowId: 'tu_workflow_id', // Workflow ID to execute
customization: getWorkflowCustomization() // null = default UI, or customize
});
Manejo de Eventos
Utiliza el hook useWorkflowEvents para escuchar los eventos del workflow:
const { workflowStart, workflowEvent, workflowStep } = useWorkflowEvents();
// Evento de inicio del workflow
useEffect(() => {
if (workflowStart) {
console.log("Workflow started success:", workflowStart.success);
if (workflowStart.error !== undefined) {
console.log("Workflow Id Error:", workflowStart.error);
}
}
}, [workflowStart]);
// Eventos del workflow
useEffect(() => {
if (workflowEvent) {
console.log("Workflow UUID:", workflowEvent.uuid, "Status:", workflowEvent.status);
if (workflowEvent.error) {
console.log("Workflow Error:", workflowEvent.error);
}
}
}, [workflowEvent]);
// Eventos de pasos
useEffect(() => {
if (workflowStep) {
console.log("Step:", workflowStep.step, "Status:", workflowStep.status);
const error = workflowStep.error;
if (error && (workflowStep.status === "error" || workflowStep.status === "failed")) {
console.log("Error - fatal:", error.fatal,
"errorId:", error.details?.errorId,
"reason:", error.details?.reason);
const extra = error.extra;
if (extra) {
if (extra.idScanStatus != null || extra.faceScanStatus != null) {
console.log("idScanStatus:", extra.idScanStatus,
"faceScanStatus:", extra.faceScanStatus);
}
if (extra.permission != null) {
console.log("Permission denied:", extra.permission);
// Cerrar la actividad/vista de workflows
closeWorkflow();
}
if (extra.secure != null) {
console.log("Is device secure:", extra.secure);
}
if (extra.isLicenseValid != null) {
console.log("Is license valid:", extra.isLicenseValid);
}
}
}
}
}, [workflowStep]);
Lanzar el Workflow
Para lanzar la pantalla de workflows, simplemente llama al hook:
<Button title="Iniciar Workflow" onPress={launchWorkflow} />
Cerrar el Workflow
Workflows se abre en una nueva activity/controller. Para cerrar, usa la función closeWorkflow():
closeWorkflow();
Personalización del SDK
Puedes personalizar la apariencia del SDK mediante la función de configuración:
import { Platform } from 'react-native';
function getWorkflowCustomization(): WorkflowCustomization {
return {
theme: {
primaryColor: '#61DBFB',
secondaryColor: '#20232A',
buttonPrimaryColor: '#61DBFB',
buttonSecondaryColor: '#20232A',
buttonTextPrimaryColor: '#FF6F61',
buttonTextSecondaryColor: '#F9A826',
backgroundColor: '#F5F5F5',
facetecOverlayColor: '#F5F5F5',
showHeaderLogo: true
},
textLocalization: {
localization: Platform.OS === 'android'
? 'localizable.xml'
: 'Localizable.strings'
},
textFont: {
headerFont: Platform.OS === 'android'
? 'montserrat_black.ttf'
: 'Montserrat-Black.ttf',
bodyFont: Platform.OS === 'android'
? 'montserrat_regular.ttf'
: 'Montserrat-Regular.ttf',
buttonFont: Platform.OS === 'android'
? 'playwrite_ar_regular.ttf'
: 'PlaywriteAR-Regular.ttf'
},
images: {
logo: 'logo.png',
cameraPermissions: 'cam.png',
uploadFaceScan: 'upload.png',
uploadFaceSuccess: 'exito_success.png',
uploadFaceError: 'error_image.png',
photoMathId: 'id.png',
photoMathIdBack: 'id_back.png',
cameraActiveTorch: 'on_flash.png',
cameraInactiveTorch: 'off_flash.png',
fingerPrint: 'fing.png',
successPage: 'success.svg',
errorPage: 'error.svg',
unavailablePage: 'error.svg',
locationPage: 'ubicacion.svg',
biometricSignPage: 'face_match.svg',
enrollFingerprintPage: 'face_match.svg',
enrollBasicPage: 'face_match.svg',
enrollFullPage: 'face_match.svg',
authPage: 'face_match.svg',
livenessPage: 'face_match.svg',
faceEnrollment3dPage: 'face_match.svg'
}
};
}
Ejemplo Completo
import React, { useEffect } from 'react';
import { View, Button, Platform } from 'react-native';
import type { WorkflowCustomization } from '@rem-tools/workflows-react-native';
import {
closeWorkflow,
useWorkflow,
useWorkflowEvents,
verifyWorkflowsLicense
} from '@rem-tools/workflows-react-native';
function getWorkflowCustomization(): WorkflowCustomization {
return {
theme: {
primaryColor: '#61DBFB',
secondaryColor: '#20232A',
buttonPrimaryColor: '#61DBFB',
buttonSecondaryColor: '#20232A',
buttonTextPrimaryColor: '#FFFFFF',
buttonTextSecondaryColor: '#FFFFFF',
backgroundColor: '#F5F5F5',
facetecOverlayColor: '#000000',
showHeaderLogo: true
},
textLocalization: {
localization: Platform.OS === 'android'
? 'localizable.xml'
: 'Localizable.strings'
},
textFont: {
headerFont: Platform.OS === 'android'
? 'montserrat_black.ttf'
: 'Montserrat-Black.ttf',
bodyFont: Platform.OS === 'android'
? 'montserrat_regular.ttf'
: 'Montserrat-Regular.ttf',
buttonFont: Platform.OS === 'android'
? 'montserrat_bold.ttf'
: 'Montserrat-Bold.ttf'
},
images: {
logo: 'logo.png',
cameraPermissions: 'camera.png',
uploadFaceScan: 'upload.png',
uploadFaceSuccess: 'success.png',
uploadFaceError: 'error.png',
photoMathId: 'id.png',
photoMathIdBack: 'id_back.png',
cameraActiveTorch: 'flash_on.png',
cameraInactiveTorch: 'flash_off.png',
fingerPrint: 'fingerprint.png',
successPage: 'success.svg',
errorPage: 'error.svg',
unavailablePage: 'unavailable.svg',
locationPage: 'location.svg',
biometricSignPage: 'biometric.svg',
enrollFingerprintPage: 'fingerprint.svg',
enrollBasicPage: 'enroll.svg',
enrollFullPage: 'enroll_full.svg',
authPage: 'auth.svg',
livenessPage: 'liveness.svg',
faceEnrollment3dPage: 'face_3d.svg'
}
};
}
export default function WorkflowScreen() {
// Verificar licencia al iniciar
useEffect(() => {
verifyWorkflowsLicense({
cert: "tu_certificado",
passphrase: "tu_passphrase"
});
}, []);
// Configurar el workflow
const launchWorkflow = useWorkflow({
baseUrl: 'https://api.rem.tools',
apiKey: 'tu_api_key',
workflowId: 'tu_workflow_id',
customization: getWorkflowCustomization()
});
// Escuchar eventos
const { workflowStart, workflowEvent, workflowStep } = useWorkflowEvents();
useEffect(() => {
if (workflowStart) {
console.log("Workflow started:", workflowStart.success);
}
}, [workflowStart]);
useEffect(() => {
if (workflowEvent) {
console.log("Workflow status:", workflowEvent.status);
}
}, [workflowEvent]);
useEffect(() => {
if (workflowStep) {
console.log("Step status:", workflowStep.status);
}
}, [workflowStep]);
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Button title="Iniciar Workflow" onPress={launchWorkflow} />
</View>
);
}
FAQ
¿Cómo obtengo el workflowId?
El workflowId se obtiene al crear un Workflow mediante nuestra API de Workflows. La respuesta de la API te proporcionará este identificador.
¿Cómo consigo el apiKey y el baseUrl?
El apiKey es proporcionado de forma privada, y el baseUrl suele ser:
https://api.rem.toolspara producción.https://api.test.rem.toolspara pruebas.
¿Cómo obtengo las credenciales de licencia?
Las credenciales de licencia (cert y passphrase) para la función verifyWorkflowsLicense serán compartidas de forma privada por tu administrador de cuenta.
¿Cómo cierro el workflow programáticamente?
Utiliza la función closeWorkflow() que se importa del SDK. Esto cerrará la activity/controller del workflow.
Si tienes alguna duda o problema con la integración del SDK, por favor contacta a soporte.



