Biometric ID - SDK Android¶
En este documento encontrará una explicación de cómo se realiza la integración con el SDK Android BiometricID
Última versión estable: 1.0.195
Pre requisitos¶
Para hacer uso del SDK se debe contar con
ApiKey válido: {{ API_KEY }}
Usuario Maven: {{ USUARIO_MAVEN }}
Password Maven: {{ PASSWORD_MAVEN }}
Correo de usuario creado desde el selfservice: {{ USUARIO_SELFSERVICE }}
Crear un Api Key¶
Para usar el sdk se necesita tener un api key válido, en caso de que tengas uno omite este paso
-
Inicia sesión en el selfservice con tus credenciales

-
Da click en la sección de Api Keys

-
Da click agregar apikey

-
Ingresa un nombre y selecciona el tipo sdk android por último escribe el application Id de tu proyecto

Instalación¶
Aplicación demo¶
Ponemos a su disposición una aplicación de ejemplo en un repositorio privado. Para clonarla, utilice el token de acceso (PAT) que le fue entregado; este corresponde al mismo {{ PASSWORD_MAVEN }} con el que accede a los artefactos de Maven:
git clone https://{{ PASSWORD_MAVEN }}@dev.azure.com/getlatam/biometricid-android-demo/_git/biometricid-android-demo
Token de acceso
El token se entrega de forma privada y permite tanto clonar este repositorio como descargar los artefactos de Maven desde el selfservice. No lo comparta ni lo publique en repositorios públicos.
Instalacion del SDK¶
1 . Vamos a settings.gradle y en la sección de repositories añadimos la sección de maven:
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven {
url 'https://pkgs.dev.azure.com/getlatam/Racsa/_packaging/BiometricID/maven/v1'
name 'BiometricID'
credentials {
username "{{ USUARIO_MAVEN }}"
password "{{ PASSWORD_MAVEN }}"
}
authentication {
basic(BasicAuthentication)
}
}
}
2 . En el build.gradle de la app en la sección de android colocamos el minSdk con un valor minimo de 22 en compileSdk y targetSdk 34, además debemos habilitar multiDex, viewBinding y añadimos las versiones kotlin
android {
compileSdk 34
defaultConfig {
minSdk 22
targetSdk 34
...
multiDexEnabled true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
compose true
viewBinding true
}
composeOptions {
kotlinCompilerExtensionVersion '1.5.1'
}
}
3 . En el build.gradle de la app en la sección de android añadimos la sección packagingOptions
android {
...
packagingOptions {
pickFirst 'lib/x86/libc++_shared.so'
pickFirst 'lib/x86_64/libc++_shared.so'
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
pickFirst 'lib/arm64-v8a/libc++_shared.so'
pickFirst 'lib/armeabi-v7a/libopencv_java4.so'
pickFirst 'lib/arm64-v8a/libopencv_java4.so'
pickFirst 'lib/arm64-v8a/libncnn.so'
pickFirst 'lib/armeabi-v7a/libncnn.so'
}
}
4 . En el build.gradle de la app en la sección de dependencies añadimos la dependencia del sdk y unas dependencias adicionales
dependencies {
//sdk
implementation(group: 'com.getgroup', name: 'integraid-sdk', version: '1.0.195')
//dependencias adicionales requeridas
implementation(group: 'getgroup.integraid', name: 'integraid-java-client', version: '1.0.24191.1')
implementation(group: 'com.getgroup', name: 'opencv', version: '4.6.0')
//android x requeridas
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.6.0'
implementation 'com.google.android.material:material:1.8.0'
}
Instalación dagger¶
El sdk necesita instalar dagger para trabajar con inyección de dependencias
1 . En el build.gradle de la app en la sección dependencies añadimos
dependencies {
//inyección de dependencias
implementation "com.google.dagger:hilt-android:2.42"
kapt "com.google.dagger:hilt-compiler:2.42"
kapt "androidx.hilt:hilt-compiler:1.0.0"
}
2 . En el build.gradle de la app en la sección plugins añadimos
plugins {
//inyeccion de dependencias
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
//plugins requeridos
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
3 . En el build.gradle de la app en la sección defaultConfig añadimos
android {
defaultConfig {
//inyeccion de dependencias
javaCompileOptions.annotationProcessorOptions.arguments['dagger.hilt.disableCrossCompilationRootValidation'] = 'true'
}
}
4 . En el build.gradle del proyecto añadimos
plugins {
id 'com.android.application' version '8.2.0' apply false
id 'com.android.library' version '8.2.0' apply false
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
}
5 . En el settings.gradle en la sección pluginManagement
pluginManagement{
//inyeccion de dependencias
resolutionStrategy {
eachPlugin {
if( requested.id.id == 'dagger.hilt.android.plugin') {
useModule("com.google.dagger:hilt-android-gradle-plugin:2.39.1")
}
}
}
}
Inyectar la configuración al sdk¶
1 . Creamos una clase llamada App que hereda de ApplicationBase y vamos a sobreescribir el metodo getSdkConfigModule
import com.getgroup.integraidsdk.ApplicationBase
import com.getgroup.integraidsdk.Domain.SdkConfig.SdkConfigModule
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class App : ApplicationBase() {
//para sobreescribir la configuración
override fun getSdkConfigModule(): SdkConfigModule {
return AppConfigModule(this)
}
}
2 . Creamos la clase AppConfigModule
import android.app.Application
import com.getgroup.integraidsdk.Domain.SdkConfig.SdkConfig
import com.getgroup.integraidsdk.Domain.SdkConfig.SdkConfigModule
class AppConfigModule(private val context: Application) : SdkConfigModule() {
override fun provideSdkConfig(): SdkConfig {
return SdkConfig(
UrlBaseApi = "{URL_API}",
ShowScore = false,
IsDebug = false,
TimeOutApi = 20 * 1000,
TimeOutMostrarDocumento = 5 * 60 * 1000,
ApiKey = "{API_KEY}",
WriteLogsConsola = false,
WriteLogsDispositivo = false,
UrlTerminosCondiciones = "{URL_TERMINOS}",
LivenessEscaneoFacial = 0.8,
ScoreMinimoEscaneoFacial = 40f,
LivenessScoreMinimoEscaneoHuella = 0.5f,
Nist2qualityMinimoEscaneoHuella = 40,
HuellaWidth = 800,
HuellaHeight = 800,
HuellaCompressionRatio = 0f,
)
}
}
3 . Vamos al manifest y asociamos la clase de la app como el contexto en la sección de application
<application
android:name=".App"
tools:replace="android:name,android:theme"
...
Compatibilidad con android con Android Studio Narwhal¶
En caso de presentarse errores de compilación se debe realizar la siguiente configuración:
-
Ir al menu File, settings, Build Tools, Gradle, en el gradle jdk se debe seleccionar la versión 17
-
En el archivo gradle.properties se debe añadir lo siguente:
org.gradle.jvmargs=--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
kapt.use.worker.api=false
kapt.include.compile.classpath=false
- Limpiar cache: Ir al menu File, Ivalidate caches, seleccionar todas las opciones y click en el botón Invalidate and restart
Definiciones¶
Variables de configuración (SdkConfig)¶
| Nombre | Tipo | Descripción |
|---|---|---|
| UrlBaseApi | String | Url del api https://apibiometricid.racsa.go.cr/bioapi |
| ShowScore | bool | Si esta en true cuando falle la verificación biometrica mostrará el score o puntaje |
| IsDebug | bool | Para definir si la app esta en debug |
| TimeOutApi | int | Asigna el tiempo máximo de espera en la conexión con el api, el valor recomendado es de 20000 milisegundos oséa 20 segundos |
| TimeOutMostrarDocumento | long | Asigna el tiempo máximo que permitirá mostrar la información del documento consultado en InformacionPersonalView |
| ApiKey | string | El token generado por el self service que tiene asociado el applicationId del proyecto, el apikey permite realizar peticiones al api |
| WriteLogsConsola | bool | Si está en true escribirá logs en la consola |
| WriteLogsDispositivo | bool | si está en true escribirá logs en el dispositivo, sin embargo se deberán declarar permisos de lectura y escritura en el manifest |
| UrlTerminosCondiciones | string | Asigna la url de terminos y condiciones que se mostrará al usuario cuando realice una consulta de documento |
| LivenessEscaneoFacial | double | Valor entre 0 y 1 entre más alto el número verificará que el escaneo se le tome a una persona para evitar fraudes, el valor recomendado es 0.8 |
| LivenessEscaneoHuella | float | Valor entre 0 y 1 entre más alto el número verificará que el escaneo se le tome a una huella real para evitar fraudes |
| ScoreMinimoEscaneoFacial | float | Valor entre 0f y 100f, es el score o puntuación mínima local de rostro que realiza el sdk antes de enviarlo al api |
| ScoreMinimoEscaneoHuella | float | Valor entre 0f y 1f, es el score o puntuación mínima local de huellas que realiza el sdk antes de enviarlo al api |
| HuellaWidth | int | Ancho de la imagen de la huella que será enviada |
| HuellaHeight | int | Alto de la imagen de la huella que será enviada |
| HuellaCompressionRatio | float | valor entre 0f y 1f, que corresponde al nivel de compresión de la imagen de la huella |
| SkipHuellaExplicacion | bool | Si está en true (valor por defecto) omite la pantalla de instrucciones de huellas e inicia el escaneo directamente. Si está en false muestra la pantalla de instrucciones antes de escanear. |
Clases¶
| Nombre | Descripción | Propiedades |
|---|---|---|
| Documento | Clase que muestra la información básica de un documento consultado | Nombre: String, Apellido: String, SegundoApellido: String, Huellas: List<HuellaEscanearEnum?>, IsVerificacionFacial: Boolean, TipoDocumentoEnum:TipoDocumentoEnum, TransactionId:String |
| TipoDocumentoEnum | Enumerador con el tipo de documento | NACIONAL, EXTRANJERO |
| ResponseException | clase que trae las excepciones internas y del api | code: Int, error: String, response: Any?,, responseBody: String?, transactionId: String, extras: Any? |
| HuellaEscanearEnum | Enumerador con los tipos de huella para escanear | PULGAR_DERECHO = 1, PULGAR_IZQUIERDO = 6, INDICE_DERECHO= 2, INDICE_IZQUIERDO = 7, MEDIO_DERECHO = 3, MEDIO_IZQUIERDO = 8, ANULAR_DERECHO = 4, ANULAR_IZQUIERDO= 9, MENIQUE_DERECHO = 5, MENIQUE_IZQUIERDO = 10 |
| VerificarFacialHuella | clase para realizar la verificación biometrica (facial o huella) | Token: String, Documento: String, NombreCompleto: String, Huella: HuellaEscanearEnum, PathHuellaWsq: String, PathFoto: String |
| InformacionPersonal | clase de información personal de un documento consultado y que haya pasado la verificación biometrica | TransactionId: String, ImagenDocumento: String, FechaExpiracion: String, NombreCompleto: String, Documento: String, ListFuentesExternas:List<FuenteExterna> |
| FuenteExterna | clase de fuente externa disponible para consultar | Id: Int, Nombre: String, Seleccionada: Boolean |
Códigos de error¶
Cada variable se encuentra en la clase ApiConstants
| Nombre | Código | Explicación |
|---|---|---|
| RESPONSE_OK | 200 | Todo fue correcto |
| RESPONSE_NOT_FOUND | 404 | No encontró el documento consultado |
| RESPONSE_ERROR_VALIDATIONS | 400 | Errores de validación, por ejemplo no se están enviando datos con el formato correcto |
| RESPONSE_INTERNAL_ERROR | 500 | Error interno del servidor |
| RESPONSE_UNAUTHORIZARED | 401 | Petición sin autorización, el token de consulta de documento expiro ó se esta enviando un Api Key sin autorización |
| RESPONSE_UNAUTHORIZARED_FORBIDDEN | 403 | Se esta enviando un Api Key inválido |
| RESPONSE_IMAGES_NOT_FOUND | 512 | Error local de la aplicación que ocurre cuando se perdió el cache de la verificación facial o huella, se debe realizar una nueva verificación |
Permisos¶
El sdk ya tiene definidos los permisos y no es necesario declararlos en el manifest de tu proyecto sin embargo a continuación se muestran
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
Diagrama de navegación¶
Cada interfaz (activity) debe usar un view que se encarga de la logica de negocio, a continuación el flujo de los activities por defecto con el nombre del view que utliza

Activities por defecto¶
Para invocar el flujo de navegación por defecto debes hacer un intent a ConsultarDocumentoActivity
val intent = Intent(this, ConsultarDocumentoActivity::class.java)
startActivity(intent)
Crea tus activities¶
Activity¶
Cada activity debe heredar de BaseActivity y arriba de la clase colocar @AndroidEntryPoint para que recibir las dependencias del sdk por ejemplo:
@AndroidEntryPoint
class CustomActivity : BaseActivity() {
}
xml¶
En el xml del activity se debe de usar el view deseado
<com.getgroup.integraidsdk.Ui.ViewDeseado
android:id="@+id/identificador"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
Evento¶
Cada view tiene una función init donde se le pasa un evento que te notificará para que puedas tomar acción, por ejemplo:
private val event = object : Evento{
override fun onEvento(): Boolean {
return super.onEvento()
}
}
Consultar documento¶
Este view permite realizar la consulta de un documento especificando el tipo de documento y aceptando los términos y condiciones, existen 2 views que solo cambiará la manera de mostrar los tipos de documento
paquete view en forma seleccionable: com.getgroup.integraidsdk.Ui.ConsultarDocumento.ConsultarDocumentoView
paquete view en forma de lista: com.getgroup.integraidsdk.Ui.ConsultarDocumento.ConsultarDocumentoSpinnerListView
| Método | Descripción |
|---|---|
init(event: ConsultarDocumentoViewEvent?, accessToken:string?, correoUsuario:String?) |
Se llama para inicializar el view, se le pasa el evento y el correo del usuario {USUARIO_SELFSERVICE} que está realizando la petición (El accessToken se envía null) |
OnActivityResult(requestCode: Int, resultCode: Int, data: Intent?) |
Se debe llamar en el onActivityResult del activity donde se usa el view, pasandole los mismos argumentos |
mostrarError(ex: ResponseException?) |
Permite mostrar un error enviando una instancia de tipo ResponseException |
mostrarError(text: String) |
Permite mostrar un error enviando una cadena de texto |
ConsultarDocumentoViewEvent¶
| Método | Descripción |
|---|---|
onLoader(isVisible: Boolean) |
Método que notifica que si se debe o no mostrar un loader |
onResultTiposDocumento(result: Result<List<TipoDocumentoEnum>>) |
Método que traerá un resultado la lista de tipos de documento disponibles para consultar o un error. Para saber si todo fue correcto preguntar result.isSuccess, para obtener la lista de tipos de documento result.getOrNull(), para obtener el error val error = result.exceptionOrNull() as ResponseException. NOTA: se debe tener en cuenta que los tipos de documento que aparecerán en el View serán los que estén configurados en el tipo de suscripción al que pertenece el cliente del Selfservice, en caso de no tener algúno habilitado entonces comunicarse con el administrador. |
onResult(result: Result<Documento?>) |
Método que traerá un resultado de Documento o un error. Para saber si todo fue correcto preguntar result.isSuccess, para obtener el documento result.getOrNull(), para obtener el error val error = result.exceptionOrNull() as ResponseException |
onEndShowError(code: Int) |
Método que notifica una vez se haya terminado de mostrar un error desde el view mConsultarDocumentoView.mostrarError(error) |
Documento encontrado¶
Este view muestra el documento encontrado y las opciones para la verificación facial o huella
paquete: com.getgroup.integraidsdk.Ui.DocumentoEncontrado.DocumentoEncontradoView
| Método | Descripción |
|---|---|
init(context: Activity, model: Documento, event: DocumentoEncontradoEvent?, escanearHuellaMano:Bool) |
inicializa el view, se le debe enviar el activity, un objeto de tipo Documento, el evento de tipo DocumentoEncontradoEvent y se pasa un valor booleano para el parametro escanearHuellaMano en caso de que sea true el sdk pide que el usuario ponga su mano y solo escanea la huella que se haya solicitado, por defecto su valor es false entonces solo se escanea el dedo requerido |
| OnActivityResult(requestCode: Int, resultCode: Int, data: Intent?) | Se debe llamar en el onActivityResult del activity enviando los mismos argumentos |
DocumentoEncontradoEvent¶
| Método | Descripción |
|---|---|
| onEscaneoFacial(): Boolean | Cuando se le da click a la verificación facial llega aquí, si se retorna el metodo super().onEscaneoFacial() entonces continua, en caso de retornar false no permite continuar con el escaneo facial |
| onEscaneoHuella(huella: HuellaEscanearEnum?): Boolean | Al dar click para escanear una huella llega aquí y entrega la huella que va a escanear, si return super().onEscaneoHuella() continua si return false entonces no realiza el escaneo |
| onVerificarEscaneoFacial(model: VerificarFacialHuella): Boolean | una vez realiza el escaneo facial o por huella si pasa el score mínimo local que se le ha configurado al sdk llega aquí, entrega un objeto para realizar la verificación biométrica, para realizar la verificación con una activity propia se retorna false |
| onShowMensajes(mensaje: String) | cada vez que el view necesite mostrar un mensaje llega aquí una cadena que se puede mostrar con un Toast por ejemplo |
Verificación biométrica (facial o huella)¶
Es un view que se comunica con el api enviando la foto o huella y retorna la información personal con las fuentes externas disponibles
paquete: com.getgroup.integraidsdk.Ui.VerificacionFacialHuella.VerificacionFacialHuellaView
| Método | Descripción |
|---|---|
| init(event: VerificacionFacialHuellaEvent?) | para inicializar el view, se le debe enviar un evento de tipo VerificacionFacialHuellaEvent |
| verificar(model: VerificarFacialHuella) | para realizar la verificación biometrica (por huella o facial), se le debe enviar el objeto que se extrae de DocumentoEncontradoView |
VerificacionFacialHuellaEvent¶
| Método | Descripción |
|---|---|
| onLoader(isVisible: Boolean) | cada vez que el view necesite mostrar o no un loader |
| onError(mensaje: String, error: ResponseException?) | aquí llegarán los errores como texto o como un objeto ResponseException |
| onResult(result: Result |
metodo que traera un objeto de tipo InformacionPersonal, para obtener el resultado val informacionPersonal = result.getOrNull() as InformacionPersonal |
Nota
No se recomienda enviar el modelo a otro activity con un intent usando extras, debido a que la longitud de la imagen en base64 puede generar una excepción
Ejemplo enviar y recibir información personal¶
//Enviar información como una variable estática
val informacionPersonal = result.getOrNull() as InformacionPersonal
InformacionPersonalActivity.EXTRAS.MODEL = informacionPersonal
val intent = Intent(baseContext, InformacionPersonalActivity::class.java)
startActivity(intent)
//Recibir información de la variable estática
@AndroidEntryPoint
class InformacionPersonalActivity : BaseActivity() {
object EXTRAS {
var MODEL: InformacionPersonal? = null
}
private var model: InformacionPersonal? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
model = EXTRAS.MODEL
if (model == null) {
finish()
showToast("No se recibió la información correctamente")
return
}
binding.mInformacionPersonalView.init(event, model!!)
}
Información personal¶
Muestra la información personal de un documento consultado (Nombres, Apellidos, Foto del documento, fecha de expiración y la opción de zoom en el documento)
paquete: com.getgroup.integraidsdk.Ui.InformacionPersonal.InformacionPersonalView
| Método | Descripción |
|---|---|
| init(event: InformacionPersonalEvent?, model: InformacionPersonal) | inicializa el view, se debe pasar un evento de tipo InformacionPersonalEvent y el objeto InformacionPersonal extraído de VerificacionFacialHuellaView. |
| Iniciara un temporizador con el tiempo que se haya configurado el sdk | |
| destroy() | detiene el temporizador y destruye el view |
InformacionPersonalEvent¶
| Método | Descripción |
|---|---|
| onMostrarFuentesExternas(model: InformacionPersonal): Boolean | cuando hay fuentes externas disponibles, el usuario puede dar click para verlas y primero pasará por aquí, para mostrarlas en un activity propio retornar false |
Fuentes externas¶
Muestra la lista de fuentes externas disponibles de un usuario, cuando el usuario de expanda la fuente externa realiza la consulta y se cargan los resultados con la posibilidad de ocultarlos
paquete: com.getgroup.integraidsdk.Ui.FuentesExternas.View.FuentesExternasView
| Método | Descripción |
|---|---|
| init(event: FuentesExternasEvent?, model:InformacionPersonal) | inicializa el view, se le pasa un evento de tipo FuentesExternasEvent y el objeto InformacionPersonal que se extrae de InformacionPersonalView |
FuentesExternasEvent¶
| Método | Descripción |
|---|---|
| onQuery(model:FuenteExterna) | Evento que notifica la fuente externa que se esta consultando |
| onResultErrorQuery(message: String, error: ResponseException?, fuenteExterna: FuenteExterna) | Evento que notifica excepciones durante la consulta de una fuente externa |
| onResultQuery(result: FuenteExternaResponseOk, fuenteExterna: FuenteExterna) | Evento que notifica trae el resultado de la consulta y la fuente externa asociada |
Casos de uso¶
En caso de querer utilizar la lógica sin interactuar con los views se puede hacer utilizar los casos de uso, para lograrlo se debe crear un ViewModel y solicitar el caso de uso desde el constructor
Ejemplo de ViewModel
//Se crea un evento para notificar del resultado
interface CustomEvent {
fun onTiposDocumentoRecived(result: List<TipoDocumentoEnum>)
fun onDocumentoRecived(result: Documento)
fun onErrorRecived(exception: Throwable)
}
//Se crea el ViewModel para interactuar con los casos de uso
@HiltViewModel
class CustomViewModel @Inject constructor(
@Named(DI.CONSULTAR_DOCUMENTO_USECASE) val useCase: IConsultarDocumentoUseCase,
) : BaseViewModel() {
private val TAG = "CustomViewModel"
private var event: CustomEvent? = null
fun init(event: CustomEvent){
this.event = event
}
fun consultarTiposDocumento(context: Context) {
viewModelScope.launch(Dispatchers.IO) {
try {
val result = useCase.ConsultarTiposDocumento(accessToken = null)
if (!result.isSuccess) {
val exception = result.exceptionOrNull()?:Exception("Ocurrio un error")
//Manejar la exception
LogsUtils.log(context, TAG, exception)
return@launch
}
val tiposDocumento = result.getOrNull() as List<TipoDocumentoEnum>
//Manejar el resultado actualizando la UI en caso de ser necesario
when (context) {
is Activity -> context.runOnUiThread {
event?.onTiposDocumentoRecived(tiposDocumento)
}
else -> {
event?.onTiposDocumentoRecived(tiposDocumento)
}
}
} catch (ex: Exception) {
LogsUtils.log(context, TAG, ex)
}
}
}
fun consultarDocumento(context: Context, tipoDocumento: TipoDocumentoEnum, numeroDocumento:String) {
viewModelScope.launch(Dispatchers.IO) {
try {
val result = useCase.Consultar(tipo=tipoDocumento, documento = numeroDocumento, correoUsuario = BuildConfig.USER_EMAIL, accessToken = null)
if (!result.isSuccess) {
val exception = result.exceptionOrNull()?:Exception("Ocurrio un error")
//Manejar la exception
LogsUtils.log(context, TAG, exception)
when (context) {
is Activity -> context.runOnUiThread {
event?.onErrorRecived(exception)
}
else -> {
event?.onErrorRecived(exception)
}
}
return@launch
}
val documento = result.getOrNull() as Documento
//Manejar el resultado actualizando la UI en caso de ser necesario
when (context) {
is Activity -> context.runOnUiThread {
event?.onDocumentoRecived(documento)
}
else -> {
event?.onDocumentoRecived(documento)
}
}
} catch (ex: Exception) {
LogsUtils.log(context, TAG, ex)
}
}
}
}
Posteriormente desde el Activity se obtiene la instancia del ViewModel y se realiza el llamado al metodo correspondiente
@AndroidEntryPoint
class ConsultarDocumentoCasoUsoActivity : BaseActivity() {
private lateinit var binding: ActivityConsultarDocumentoCasosUsoBinding
private val viewModel: CustomViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityConsultarDocumentoCasosUsoBinding.inflate(layoutInflater)
setContentView(binding.root)
viewModel.init(event)
binding.btnConsultarTiposDocumento.setOnClickListener {
viewModel.consultarTiposDocumento(baseContext)
}
binding.btnConsultarExtranjero.setOnClickListener {
viewModel.consultarDocumento(baseContext, TipoDocumentoEnum.EXTRANJERO, binding.txtDocumento.text.toString())
}
binding.btnConsultarNacional.setOnClickListener {
viewModel.consultarDocumento(baseContext, TipoDocumentoEnum.NACIONAL, binding.txtDocumento.text.toString())
}
}
private val event = object : CustomEvent {
override fun onTiposDocumentoRecived(result: List<TipoDocumentoEnum>) {
binding.txtResultado.setTextColor(getColor(com.getgroup.demo.R.color.green))
binding.txtResultado.text = JsonConvertUtils.serialize(result)
}
override fun onDocumentoRecived(result: Documento) {
binding.txtResultado.setTextColor(getColor(com.getgroup.demo.R.color.green))
binding.txtResultado.text = JsonConvertUtils.serialize(result)
//continuar el flujo por medio de los views
/*val intent =
Intent(baseContext, DocumentoEncontradoManualActivity::class.java).apply {
putExtra(
DocumentoEncontradoManualActivity.EXTRAS.EXTRA_JSON,
JsonConvertUtils.serialize(result)
)
}
startActivity(intent)*/
}
override fun onErrorRecived(exception: Throwable) {
binding.txtResultado.setTextColor(getColor(com.getgroup.demo.R.color.red))
if (exception is ResponseException) {
val responseException = exception as ResponseException
//Se entregan más detalles de la excepcion
}
binding.txtResultado.text = JsonConvertUtils.serialize(exception)
}
}
}
Consultar Documento UseCase¶
Dependencia¶
@Named(DI.CONSULTAR_DOCUMENTO_USECASE) private val useCase: IConsultarDocumentoUseCase
Funciones¶
- Consultar tipos de documentos
suspend fun ConsultarTiposDocumento(
accessToken: String? //Token de acceso se envia null
): Result<List<TipoDocumentoEnum>>
- Consultar documento
suspend fun Consultar(
tipo: TipoDocumentoEnum, //TipoDocumentoEnum.NACIONAL | TipoDocumentoEnum.EXTRANJERO
documento: String, //Número de documento
accessToken: String?, //Token de acceso se envia null
correoUsuario: String?, //Enviar el correo de usuario que está realizando la solicitud
): Result<Documento>
Escaneo Facial UseCase¶
Dependencia¶
@Named(DI.ESCANEO_FACIAL_USECASE) val useCase: IEscaneoFacialUseCase
Funciones¶
- Escanear
fun EscaneoFacial(
context: Activity,//Activity que realiza la solicitud
listener: FaceCapturedListener?, //Evento para recibir la respuesta del escaneo
usarCamaraFrontal: Boolean, //true | false
)
interface FaceCapturedListener {
fun OnError(errorMessage: String, details: FaceCapturedDetail?)// Fallo el escaneo o no se cumplio el score requerido
fun OnResult(keyImage: String, details: FaceCapturedDetail)// Escaneo correcto
}
class FaceCapturedDetail(
val score: Float,// Score resultante del escaneo
val scoreLimit: Float,// Limite configurado sdkConfig.ScoreMinimoEscaneoFacial
)
Ejemplo¶
useCase.EscaneoFacial(this, mFaceCaptureListener, true)
private var mFaceCaptureListener = object : FaceCapturedListener{
override fun OnError(
errorMessage: String,
details: FaceCapturedDetail?
) {
val message = if(!MainUtils.isNullOrEmpty(errorMessage)) errorMessage else "Intenta de nuevo por favor"
showMensajes(
message
)
}
override fun OnResult(
keyImage: String,
details: FaceCapturedDetail
) {
val nombreCompleto =
modelDocumento.Nombre + " " + modelDocumento.Apellido + " " + modelDocumento.SegundoApellido
val verificarFacialHuellaModel = VerificarFacialHuella(
modelDocumento.Token,
modelDocumento.Documento,
nombreCompleto,
null,
"",
keyImage
)
val doAction =
event?.onVerificarEscaneoFacial(verificarFacialHuellaModel) ?: true
//continuar con la verificación
useCaseVerificacionFacialHuella.VerificarIntent(
_uiState.value.activity!!,
verificarFacialHuellaModel,
_uiState.value.showScore
)
}
Escaneo Huellas UseCase¶
Dependencia¶
@Named(DI.ESCANEO_HUELLA_USECASE) val useCase: IEscaneoHuellaUseCase
Funciones¶
- Escanear huella
fun EscanearHuella(
context: Activity,//Activity que realiza la solicitud
documento: String,//Número de documento
huella: HuellaEscanearEnum?,//Tipo de huella solicitada
listener: FingerCapturedListener?,//Evento para recibir la respuesta del escaneo
): Result<Boolean>
fun EscanearHuellaMano(
context: Activity,//Activity que realiza la solicitud
documento: String,//Número de documento
huella: HuellaEscanearEnum?,//Tipo de huella solicitada
listener: FingerCapturedListener?,//Evento para recibir la respuesta del escaneo
): Result<Boolean>
interface FingerCapturedListener {
fun OnError(errorMessage: String, details: FingerCapturedDetail?) // Fallo el escaneo o no se cumplio el score o calidad requerida
fun OnResult(keyImage: String, details: FingerCapturedDetail) // Escaneo correcto
}
class FingerCapturedDetail(
val score: Float,// score resultante
val scoreLimit: Float,// sdkConfig.LivenessScoreMinimoEscaneoHuella
val quality: Int,// quality resultante
val qualityLimit: Int// sdkConfig.QualityMinimoEscaneoHuella
)
Ejemplo¶
useCase.EscanearHuellaMano(this, modelDocumento.Documento, modelDocumento.Huellas[0], mFingerCaptureListener, true)
private var mFingerCaptureListener = object : FingerCapturedListener {
override fun OnError(
errorMessage: String,
details: FingerCapturedDetail?
) {
val message = if(!MainUtils.isNullOrEmpty(errorMessage)) errorMessage else "Intenta de nuevo por favor"
showMensajes(
message
)
}
override fun OnResult(
keyImage: String,
details: FingerCapturedDetail
) {
val nombreCompleto =
modelDocumento.Nombre + " " + modelDocumento.Apellido + " " + modelDocumento.SegundoApellido
val verificarFacialHuellaModel = VerificarFacialHuella(
modelDocumento.Token,
modelDocumento.Documento,
nombreCompleto,
_uiState.value.huellaEscanear,
keyImage,
""
)
//Continuar con la verificación
useCaseVerificacionFacialHuella.VerificarIntent(
_uiState.value.activity!!,
verificarFacialHuellaModel,
_uiState.value.showScore
)
}
}
IImagesUseCase¶
Para realizar la verificación facial o huellas no envia directamente la imagen sino una llave (key) en caso de querer obtener la imagen en base64 se hace por medio de la dependencia IImagesUseCase
interface IImagesUseCase {
fun set(base64: String?): String //Se envia el base64 y retorna la key
fun get(key: String): String//Para obtener la imagen en base64 por medio de la key
}
Dependencia¶
@Named(DI.IMAGES_USECASE) val useCase: IImagesUseCase
Verificacion Facial o Huella UseCase¶
Dependencia¶
@Named(DI.ESCANEO_HUELLA_USECASE) private val useCase: IVerificacionFacialHuellaUseCase
Funciones¶
- Verificar
class VerificarFacialHuella(
var Token: String="",//Token
var Documento: String="",//Número de documento
var NombreCompleto: String="",//Nombre
var Huella: HuellaEscanearEnum?=null,//Tipo de huella
var PathHuellaWsq: String?=null,//Llave de la huella en wsq, se obtiene con LocalDataSource
var PathFoto: String?=null,//Llave de la foto, se obtiene con LocalDataSource
)
suspend fun Verificar(
model: VerificarFacialHuella//
): Result<InformacionPersonal>
Consultar fuentes externas UseCase¶
Dependencia¶
@Named(DI.FUENTE_EXTERNA_USECASE) private val useCase: IFuenteExternaUseCase
Funciones¶
- Consultar
class ConsultarFuenteExterna (
val FuenteExternaTipo:ExternalSourceEnumeration,//Tipo de fuente externa CCSS | CST
val AccessTokenFuentesExternas:String//Enviar token obtenido del modelo InformacionPersonal después de pasar la verificación
)
suspend fun Consultar(
request: ConsultarFuenteExterna//Enviar instancia de ConsultarFuenteExterna
): Result<FuenteExternaResponseOk>
Otras funciones¶
Cambiar tipografías¶
Existen 2 tipografía en los views bold y regular, para reemplazarlas solo se debe crear una carpeta font dentro de la carpeta res y pegarlas con los nombres font_bold.ttf y font_regular.ttf

Cambiar textos¶
En el archivo de strings del proyecto se pueden cambiar los valores de estos strings
<string name="doc_nacional">Nacional</string>
<string name="doc_extranjero">Extranjero Residente</string>
<string name="numero_identificacion">Número de identificación</string>
<string name="consultar">Consultar</string>
<string name="regresar">Regresar</string>
<string name="wait">Espera un momento por favor...</string>
<string name="verificacion_facial">Reconocimiento Facial</string>
<string name="verificacion_huella">Verificación por huella</string>
<string name="seleccione_tipo_verificacion">Selecciona el tipo de verificación para</string>
<string name="intentar_de_nuevo">Intentar de nuevo</string>
<string name="verificar">Verificar</string>
<string name="necesitas_conceder_permisos">Necesitas conceder permisos</string>
<string name="consultar_mas_informacion">Consultar más información</string>
<string name="aceptar">Aceptar</string>
<string name="informacion_eliminada">La información ha sido eliminada del dispositivo</string>
<string name="antes_continuar">Antes de continuar:</string>
<string name="antes_continuar_lea">Antes de continuar:\n\nPor favor, lea atentamente lo siguiente.\nAl continuar, usted confirma que está de acuerdo con las siguientes declaraciones:</string>
<string name="terminos_condiciones">Ver términos y condiciones</string>
<string name="acepto_terminos">He leído y estoy de acuerdo con estos términos y condiciones</string>
<string name="confirmar_ciudadano">Por favor confirmar que el ciudadano:</string>
<string name="no_documento">No. Documento</string>
<string name="esta_confirmando">Esta confirmando que esta de acuerdo con las siguientes declaraciones</string>
<string name="declaraciones">Acepta dar su consentimiento para usar su fotografía (Selfie) y Huellas dactilares para verificar su identidad.\n\nHa leido y aceptado la politica de privacidad\n\nHa leido y acepta las condiciones de uso</string>
<string name="autorizo">Autorizo</string>
<string name="ok_">Ok</string>
<string name="exp">EXP:</string>
<string name="No">No:</string>
<string name="limpiar">Limpiar</string>
<string name="camara_frontal">Cámara frontal</string>
<string name="camara_trasera">Cámara trasera</string>
<string name="no_hay_fuentes">No hay fuentes para consultar</string>
<string name="transaccion_copiada">¡Transacción copiada!</string>
<string name="transaccion">Transacción</string>
<string name="identificacion">Identificación</string>
<string name="confirmar">Confirmar</string>
<string name="iniciar_nueva_consulta">Iniciar nueva consulta</string>
<string name="doble_click_zoom">Doble click para zoom</string>
<string name="cuatro_dedos">4 dedos de la mano</string>
<string name="los_pulgares">Los Pulgares</string>
<string name="el_pulgar_de_mano">El Pulgar de la mano</string>
<string name="derecha">Derecha</string>
<string name="izquierda">Izquierda</string>
<string name="ubique">Ubique</string>
<string name="acerque_su_mano">Acerque su mano</string>
<string name="aleje_su_mano">Aleje su mano</string>
<string name="enfoque_mano">Mueva su mano para enfocar</string>
<string name="mantenga_mano_quita">Mantenga su mano quieta</string>
<!--sdk facial-->
<string name="camera_close_message">Acercate a la cámara</string>
<string name="camera_far_message">Alejate un poco de la cámara</string>
<string name="camera_up_message">Sube la cámara hacia ti</string>
<string name="camera_down_message">Baja la cámara hacia ti</string>
<string name="open_eyes_message">Abre los ojos y mira a la cámara</string>
<string name="multiple_faces_detected">Multiples rostros detectados</string>
<string name="detection_failed_message">Detección de rostro fallida</string>
<string name="no_faces_detected_message">No se reconoce un rostro</string>
<string name="look_straight_message">Mira recto</string>
<string name="mask_detected_message">Máscara detectada</string>
<string name="sunglasses_detected_message">Gafas detectadas</string>
<string name="eyes_closed_message">Abre los ojos</string>
<string name="hold_still_message">Quedate quieto</string>
<string name="spoof_detected_message">Falsificación detetada</string>
<string name="ok_str">Capturando...</string>
<string name="please_wait">Por favor espera...</string>
<string name="network_activity_error">Algo va mal, por favor intenta de nuevo</string>
<string name="label_occulusion">Oclusión</string>
<string name="label_eye_close">Ojo cerrado</string>
<string name="label_livehness">Liveness</string>
<string name="hold_still_and_center">Centra tu rostro y quédate quieto</string>
<string name="te_queda">"Te queda: "</string>
<string name="error_documento_no_encontrado">El documento no esta disponible para consulta</string>
<string name="error_generico">Ocurrio un error, comuniquese con el administrador</string>
<string name="error_conexion">Error en la conexión, intente de nuevo ó comuniquese con el administrador</string>
<string name="error_no_autorizado">Su sesión ha expirado</string>
<string name="error_apikey_revocado">No estas autorizado para esta consulta [Apikey revocado]</string>
<string name="error_no_autorizado_forbidden">No estas autorizado para esta consulta [Apikey invalido]</string>
<string name="error_req_identificacion">Ingrese el número de identificación</string>
<string name="error_req_tipo_identificacion">Seleccione el tipo de documento</string>
<string name="error_timeout">La solicitud excedió el tiempo máximo de espera</string>
<string name="error_unauthorizes_verificacion">El token expiro por favor realice la consulta del documento nuevamente</string>
<string name="error_intentos_maximos">Se alcanzaron la cantidad de intentos máximos</string>
<string name="error_no_coincidencias">No se encontraron coincidencias ó no alcanzó el rango de verificación mínimo</string>
<string name="error_imagenes_cache">Ocurrio un error con la memoria del disposito, se necesita realizar la verificación facial o de huellas nuevamente</string>
<string name="error_req_terminos">Debes aceptar los terminos y condiciones</string>
<string name="error_score">No se alcanzo el score mínimo, intentalo de nuevo</string>
<string name="error_req_firma">Necesitas firmar para autorizar</string>
<string name="error_seguridad">Error de seguridad</string>
<string name="error_desconocido">Error desconocido</string>
<string name="error_copiar_transaccion">Ocurrio un error al copiar la transacción</string>
<string name="error_cargar_pagina">Ocurrio un error al cargar la pagina</string>
<string name="error_tomar_captura">Ocurrio un error al tomar la captura</string>
<string name="error_liveness">No se identificó una persona</string>
<string name="error_response">Error en el servidor, por favor intenta de nuevo</string>
<string name="error_fuente_no_seleccionada">Debes seleccionar al menos una fuente</string>
<string name="error_servicio">Ocurrio un error con el servicio</string>
<string name="cargar">Cargar</string>
<string name="error_fuente_externa_not_found">La persona no fue encontrada</string>
<string name="si">Si</string>
<string name="no">No</string>
<string name="error_token_expirado_fuentes_externas">El token ha expirado, necesitas realizar una nueva consulta</string>
<string name="identidad_verificada">Identidad Verificada</string>
<string name="id">ID:</string>
<string name="info_adicional">Información adicional para:</string>
<string name="CCSS_descripcion">Caja Costarricense de Seguro Social</string>
<string name="CST_descripcion">Condición Tributaria</string>
<string name="pulgar_derecho">Dedo Pulgar</string>
<string name="indice_derecho">Dedo Indice</string>
<string name="medio_derecho">Dedo Medio</string>
<string name="anular_derecho">Dedo Anular</string>
<string name="menique_derecho">Dedo Meñique</string>
<string name="pulgar_izquierdo">Dedo Pulgar</string>
<string name="indice_izquierdo">Dedo Indice</string>
<string name="medio_izquierdo">Dedo Medio</string>
<string name="anular_izquierdo">Dedo Anular</string>
<string name="menique_izquierdo">Dedo Meñique</string>
<!--pantalla de instrucciones de huellas-->
<string name="instructions">. Asegúrate de estar en un área bien iluminada.\n. Coloca los dedos de tu mano frente a la cámara como se te indique.\n. Sigue las instrucciones.</string>
<string name="siguiente">Siguiente</string>
<string name="cancelar_captura">Cancelar</string>
<string name="label_left_thumb">Pulgar izquierdo</string>
<string name="label_right_thumb">Pulgar derecho</string>
<string name="label_left_slap_num_fingers">mano izquierda %1$d dedos</string>
<string name="label_right_slap_num_fingers">mano derecha %1$d dedos</string>
<string name="label_left_index_n_middle_fingers">dedo índice y dedo medio izquierdos</string>
<string name="label_right_index_n_middle_fingers">dedo índice y dedo medio derechos</string>
<string name="label_left_ring_n_little_fingers">dedo anular y meñique izquierdos</string>
<string name="label_right_ring_n_little_fingers">dedo anular y meñique derechos</string>
<string name="label_more_than_one_finger_detected">Se detectó más de 1 dedo</string>
<string name="label_more_than_n_fingers_detected">Se detectó más de %1$d dedos</string>
<string name="label_init_sdk_failed">Error al inicializar el SDK. Código: (%1$d)</string>
Pantalla de instrucciones de huellas¶
Antes del escaneo de huellas, se puede mostrar una pantalla de instrucciones al usuario. El SDK permite saltar esta pantalla, personalizar sus colores, el fondo y los textos.
Saltar la pantalla de instrucciones¶
Configura el parámetro SkipHuellaExplicacion en SdkConfig:
true(por defecto): omite la pantalla y va directamente al escaneo.false: muestra la pantalla de instrucciones antes de escanear.
return SdkConfig(
...
SkipHuellaExplicacion = false, // muestra la pantalla de instrucciones
)
Personalizar colores¶
Añade los siguientes colores en tu archivo res/values/colors.xml:
<resources>
<!-- Color de la barra superior -->
<color name="finger_preview_toolbar_color">#05becb</color>
<!-- Color del ícono/texto en la barra superior -->
<color name="finger_preview_toolbar_icon_color">#FFFFFF</color>
<!-- Color del texto de instrucciones -->
<color name="finger_preview_instructions_text_color">#5e5e5e</color>
<!-- Color de fondo del botón "Siguiente" -->
<color name="finger_preview_next_button_color">#05becb</color>
<!-- Color del texto del botón "Siguiente" -->
<color name="finger_preview_next_button_text_color">#FFFFFF</color>
<!-- Color de fondo del botón "Cancelar" -->
<color name="finger_preview_cancel_button_color">#FFFFFF</color>
<!-- Color del texto del botón "Cancelar" -->
<color name="finger_preview_cancel_button_text_color">#05becb</color>
</resources>
Personalizar el fondo¶
Crea un drawable background_plain.xml en res/drawable/ para reemplazar el fondo de la pantalla. Por ejemplo, para poner un fondo blanco con una imagen de marca:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<color android:color="@android:color/white" />
</item>
<item android:drawable="@drawable/img_background" />
</layer-list>
Novedades / Historial de versiones¶
Resumen de las mejoras incluidas en cada versión de la aplicación Biometric ID RACSA.
Versión 1.63 — 25/05/2026¶
Mejora en la velocidad de captura
Se optimizaron los algoritmos de detección y procesamiento de imagen, reduciendo el tiempo de respuesta entre la colocación del dedo y la confirmación de captura exitosa.
Opción para captura de forma vertical
Se activa la posibilidad de captura de los dedos desde el frente del dispositivo, aumentando el área de captura en la pantalla. Se permite la captura únicamente en modo vertical (Portrait).
Optimización por dispositivo
Se mejora la experiencia de captura de huellas en los dispositivos Samsung Galaxy S23 y Tablet Lenovo Tab K11.
Versión 1.60 — 30/04/2026¶
Mejora en la velocidad de captura
Aumento en la rapidez de la autocaptura de huellas. Se mantienen las guías visuales de posicionamiento del dedo en pantalla durante todo el proceso.
Validación cantidad de dígitos tanto para Cédula como para DIMEX
La consulta de Nacionales exige 9 caracteres (cédula) y la de Extranjeros 12 caracteres (DIMEX) antes de iniciar la transacción, evitando errores por datos incompletos.
Tiempo de inactividad de sesión ampliado
El inicio de sesión permanece activo hasta 8 horas de inactividad antes de solicitar nuevamente las credenciales por seguridad.