apacuana_sdk_flutter 0.2.1
apacuana_sdk_flutter: ^0.2.1 copied to clipboard
Apacuana SDK Core proporciona una interfaz para interactuar con los servicios de Apacuana.
apacuana_sdk_flutter #
Apacuana SDK para Flutter es un wrapper de alto nivel construido sobre apacuana_sdk_core_dart. Proporciona un Provider y un Notifier (basado en ChangeNotifier) que simplifican la integración de los servicios de Apacuana en apps Flutter, manejando:
- Inicialización del SDK y estado de servicio.
- Generación de certificados digitales en el dispositivo.
- Firma electrónica de documentos.
- Consulta del estado de certificados y documentos.
- Gestión de revocaciones.
- Flujos de Face Liveness con un modal embebido.
Este paquete abstrae detalles de estado, modales y almacenamiento local cifrado, exponiendo una API declarativa y fácil de usar desde widgets.
Índice #
- Instalación
- Inicialización rápida
- Arquitectura: Provider + Notifier
- Configuración del SDK (config)
- API del Notifier (métodos y ejemplos)
- getConfig
- getCustomer
- generateCert
- isCertificateInDevice
- getCertStatus
- getCertTypes
- getRequerimentsByTypeUser
- addSigner
- getDocsByCustomer
- signDocument
- startLivenessCheck
- validateFaceLiveness
- requestRevocation
- getRevocationReasons
- createApacuanaUser
- uploadSignatureVariant
- getSignatureVariant
- deleteSignatureVariant
- Tipos útiles (ApiResult, CertData, DocsQuery)
- Permisos y configuración de plataforma
- Buenas prácticas y manejo de errores
- Ejemplo de app
- Licencia
Instalación #
En tu pubspec.yaml:
dependencies:
flutter:
sdk: flutter
provider: ^6.0.0
dio: ^5.4.0
apacuana_sdk_flutter: ^0.1.0 # Este paquete
Luego:
flutter pub get
Inicialización rápida #
Envuelve tu aplicación con el ApacuanaProvider. Este inicializa automáticamente el core cuando apiUrl y apiKey están presentes, y presenta los modales del SDK sobre tu UI.
Parámetros clave del config:
apiUrl: URL base de la API de Apacuana.secretKey: Llave secreta del cliente.apiKey: Clave pública de API (identifica tu integración).verificationId: Identificador de verificación inicial (usado en algunos flujos como onpremise).customerId(opcional): Usuario ya autenticado en tu sistema; si se provee el SDK intentará cargar su contexto.encryptionKey: Clave simétrica para cifrar datos sensibles localmente (elige una distinta por entorno, 16/24/32 bytes para AES).integrationType: Tipo de integración (ej.ONBOARDING, puede cambiar según el flujo que estés habilitando).
Ejemplo completo de inicialización (entorno dev):
import 'package:flutter/material.dart';
import 'package:apacuana_sdk_flutter/providers/apacuana_provider.dart';
import 'package:apacuana_sdk_flutter/providers/apacuana_notifier.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
// Puedes obtener el customerId de un login previo o almacenamiento seguro
final String? customerId = null; // Reemplaza con tu lógica
final config = {
'apiUrl': 'https://api.dev.apacuana.3dlinkweb.com',
'secretKey': 'fvTaYQzi1tubvKVZ2P9Ev3nw7v9WyEGh5k7PM9HDz8AB9J9uygJf76ihW9mTa8T08pv4Ww7XLY9r4B5KDc4BDikMJ6KGUJnX85CNh6QwDvBrS1apb5e3VVTWgHug7Y5f',
'apiKey': '07NTZW6cCgSCKjhzSe1UJ2uJ6fTXHaAFKcfDtJqV5Vnhg0h2BitNr6iYCeM7ZZ7CpcAVcQqk4EP0VmxvWPkTgBczpa8mGZjSU2V4zr56CpqLUM1AfjvXw79Gt4nppiTW',
'verificationId': '53d1daa5-cc7a-4a70-ae46-1a3e8a405f7a',
if (customerId != null && customerId.isNotEmpty) 'customerId': customerId,
'encryptionKey': 'estaesmiclavesecretaAES256123456',
'integrationType': 'ONBOARDING',
};
return MaterialApp(
title: 'Apacuana Demo',
builder: (context, child) {
return ApacuanaProvider(
config: config,
child: child ?? const SizedBox.shrink(),
);
},
home: const HomePage(),
);
}
}
// Cómo obtener el notifier:
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
final notifier = ApacuanaProvider.of(context, listen: true);
return Scaffold(
appBar: AppBar(title: const Text('Demo Apacuana')),
body: Center(child: Text('Estado SDK: ${notifier.status}')),
);
}
}
Arquitectura: Provider + Notifier #
El paquete expone:
ApacuanaProvider(Widget): inyecta y gestiona unApacuanaNotifieren el árbol de widgets, inicializa el core y renderiza un modal propio (ApacuanaModal) por encima de tu UI cuando el SDK lo requiere (por ejemplo, para el flujo de liveness).ApacuanaNotifier(ChangeNotifier): contiene el estado del SDK y los métodos de alto nivel para llamar a los servicios (certificados, firmas, liveness, revocaciones, etc.). Es el punto de entrada en tu UI para realizar operaciones.
Ventajas:
- Inicialización automática.
- Un solo punto de acceso al SDK desde la UI (
ApacuanaProvider.of(context)). - Modal gestionado por el SDK (aparecerá cuando el flujo lo requiera).
Configuración del SDK (config) #
El config que pasas al ApacuanaProvider es un Map<String, dynamic> que el core usa para inicializarse:
apiUrl(String, requerido): URL base del backend.apiKey(String, requerido): clave de API si tu backend la requiere.encryptionKey(String, recomendado): clave para cifrar datos sensibles (claves privadas, certificados) en el dispositivo.customerId(String, opcional): si ya tienes sesión de usuario, permite inicializar con token y userData.- Otros campos que tu core soporte (p. ej. entorno, timeouts, etc.).
El Provider hace:
- Si
apiUrlyapiKeyexisten y no están vacíos:notifier.initCore(config). - Si
customerIdestá presente, el core intentará recuperar token/userData y configurar autenticación.
API del Notifier (métodos y ejemplos) #
Para obtener el notifier:
final apn = ApacuanaProvider.of(context);
El estado del servicio:
apn.status:ApacuanaServiceStatus(idle | initializing | ready | error)apn.error: último error (si hubo) durante la init.
Importante: muchas operaciones requieren que el SDK esté ready.
getConfig #
- Firma:
Future<SdkConfig?> getConfig() - Requiere: SDK inicializado.
- Retorno: objeto de configuración actual (SdkConfig) o null si falla.
Ejemplo:
final cfg = await apn.getConfig();
debugPrint('customerId: ${cfg?.customerId}, apiUrl: ${cfg?.apiUrl}');
getCustomer #
- Firma:
Future<ApacuanaSuccess> getCustomer() - Requiere: customerId (sesión)
- Retorno:
ApacuanaSuccesscondatacontendo token/userData (estructura exacta desde el core).
Ejemplo:
final res = await apn.getCustomer();
debugPrint('getCustomer success: ${res.success}, data: ${res.data}');
generateCert #
- Firma:
Future<ApiResult> generateCert({ required String pin, bool enableSaveWithBiometrics = false }) - Requiere: SDK en estado
readyy customerId. - Parámetros:
pin(String): PIN para proteger el uso local de la clave/cert.enableSaveWithBiometrics(bool): si true, solicitará autenticación biométrica y asociará el usuario a biometría para accesos futuros.
- Proceso (resumen):
- Genera un par de llaves RSA en el dispositivo.
- Construye un CSR y lo cifra (según flujo).
- Llama al core
generateCert. - Cifra y guarda en el dispositivo la clave privada, el certificado y el PIN.
- Retorno:
ApiResultcondata: CertData(cert y certifiedId) cuando aplica.
Ejemplo:
final res = await apn.generateCert(pin: '1234', enableSaveWithBiometrics: false);
if (res.success) {
final certData = res.data as CertData?;
debugPrint('Cert creado: id=${certData?.certifiedId}');
} else {
debugPrint('Error: ${res.message}');
}
isCertificateInDevice #
- Firma:
Future<bool> isCertificateInDevice() - Requiere: SDK en
ready. - Retorno: true si hay un certificado guardado cifrado en el dispositivo.
Ejemplo:
final hasCert = await apn.isCertificateInDevice();
getCertStatus #
- Firma:
Future<Map<String, dynamic>> getCertStatus() - Requiere: SDK
readyy customerId. - Descripción: Pregunta al core el estado del certificado (normaliza la respuesta a Map encodable).
- Retorno: Map con
success,data, etc.
Ejemplo:
final status = await apn.getCertStatus();
debugPrint(status.toString());
getCertTypes #
- Firma:
Future<dynamic> getCertTypes() - Requiere: público (no requiere customerId).
- Retorno: tipos de certificados disponibles (estructura según core).
Ejemplo:
final types = await apn.getCertTypes();
getRequerimentsByTypeUser #
- Firma:
Future<dynamic> getRequerimentsByTypeUser({ required int type }) - Requiere: público.
- Parámetros:
type(int): tipo de usuario.
- Retorno: requisitos para el tipo de usuario.
Ejemplo:
final reqs = await apn.getRequerimentsByTypeUser(type: 1);
addSigner #
- Firma:
Future<dynamic> addSigner(dynamic signer) - Requiere: customerId.
- Parámetros:
signer(Map): datos del firmante (estructura definida por tu backend).
- Retorno: respuesta del core.
Ejemplo:
final res = await apn.addSigner({
'name': 'Juan',
'doc': '12345678',
});
getDocsByCustomer #
- Firma:
Future<dynamic> getDocsByCustomer({ required int page, required int size, int? status }) - Requiere: customerId.
- Parámetros:
page(int) > 0size(int) > 0status(int?) opcional
- Retorno: documentos paginados del usuario.
Ejemplo:
final res = await apn.getDocsByCustomer(page: 1, size: 20, status: null);
signDocument #
- Firma:
Future<ApiResult<dynamic>> signDocument({ required Map<String, dynamic> signature, required String pin, bool enableBiometric = false }) - Requiere: customerId, certificado y clave privada almacenados.
- Parámetros:
signature(Map): incluye como mínimo:id(String): id de la firma o del documento.positions(List- opcionalmente
document(String base64) si el flujo usa documento embebido.
pin(String): PIN del usuario para autorizar la firma.enableBiometric(bool): si true y el usuario tiene biometría habilitada, solicitará autenticación biométrica.
- Retorno:
ApiResultcon la respuesta del core.
Ejemplo:
final signature = {
'id': 'signature-id',
'positions': [{'page':1,'x':100,'y':200}],
};
final res = await apn.signDocument(signature: signature, pin: '1234');
startLivenessCheck #
- Firma:
Future<Map<String, dynamic>> startLivenessCheck() - Requiere: customerId.
- Descripción: Crea una sesión de liveness (via core), abre un modal con un WebView interno (
WebViewLiveness), y al completar valida con el backend. Cierra el modal automáticamente. - Retorno: Map con resultado normalizado (incluye
success,data, etc.). Puede devolver error por timeout o cancelación.
Ejemplo:
final res = await apn.startLivenessCheck();
if (res['success'] == true) {
// éxito
}
validateFaceLiveness #
- Firma:
Future<dynamic> validateFaceLiveness({ required String sessionId }) - Requiere: customerId.
- Parámetros:
sessionId(String): id de sesión creada previamente.
- Retorno: respuesta del core.
Ejemplo:
final res = await apn.validateFaceLiveness(sessionId: 'SESSION_ID');
requestRevocation #
- Firma:
Future<dynamic> requestRevocation(int reasonCode) - Requiere: customerId.
- Parámetros:
reasonCode(int): motivo de revocación.
- Retorno: respuesta del core.
Ejemplo:
final res = await apn.requestRevocation(3);
getRevocationReasons #
- Firma:
Future<dynamic> getRevocationReasons() - Requiere: customerId.
- Retorno: lista/mapa de motivos de revocación.
Ejemplo:
final res = await apn.getRevocationReasons();
createApacuanaUser #
- Firma:
Future<dynamic> createApacuanaUser(Map<String, dynamic> userData) - Requiere: público.
- Parámetros:
userData(Map): campos del usuario. Mínimos típicos:kinddoc,doc,email,name,lastname,phone(según tu backend).
- Retorno: respuesta del core.
Ejemplo:
final payload = {
'kinddoc': 'V',
'doc': '12345678',
'email': 'usuario@correo.com',
'name': 'Nombre',
'lastname': 'Apellido',
'phone': 4141234567,
};
final res = await apn.createApacuanaUser(payload);
uploadSignatureVariant #
- Firma:
Future<dynamic> uploadSignatureVariant({ required MultipartFile file }) - Requiere: customerId.
- Parámetros:
file(MultipartFile dedio): imagen de la firma.
- Retorno: respuesta del core.
Ejemplo:
import 'package:dio/dio.dart';
final file = await MultipartFile.fromFile('/path/firmita.png', filename: 'firmita.png');
final res = await apn.uploadSignatureVariant(file: file);
getSignatureVariant #
- Firma:
Future<dynamic> getSignatureVariant() - Requiere: customerId.
- Retorno: imagen/variant de firma del usuario (estructura según backend).
Ejemplo:
final res = await apn.getSignatureVariant();
deleteSignatureVariant #
- Firma:
Future<dynamic> deleteSignatureVariant() - Requiere: customerId.
- Retorno: resultado de la eliminación.
Ejemplo:
final res = await apn.deleteSignatureVariant();
Tipos útiles (ApiResult, CertData, DocsQuery) #
El Notifier expone y utiliza modelos simples para empaquetar resultados:
class ApiResult<T> {
final bool success;
final String message;
final T? data;
final String? code;
final dynamic error;
// ...
}
class CertData {
final String cert;
final String certifiedId;
// ...
}
class DocsQuery {
final int page;
final int size;
final int? status;
// ...
}
ApiResult<T>: respuesta segura para mostrar en UI (success,message,data,code,error).CertData: payload específico de certificados.DocsQuery: helper para consultas paginadas de documentos.
Permisos y configuración de plataforma #
Dependiendo de qué flujos uses, revisa:
Android (AndroidManifest.xml):
- Cámara (liveness, si usa cámara), almacenamiento si usas FilePicker para pruebas.
- Internet (WebViewLiveness).
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA"/>
iOS (Info.plist):
- Descripciones de uso:
<key>NSCameraUsageDescription</key>
<string>Usamos la cámara para verificar la vivacidad.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Se requiere acceso a la galería si subes una firma desde fotos.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Se requiere acceso a la galería si subes una firma desde fotos.</string>
WebView (si usas WebViewLiveness):
- Asegúrate de tener el paquete de WebView requerido y configuración mínima según su documentación (p. ej.,
webview_fluttero el que utilice tu widget de liveness).
Biometría:
- Si habilitas biometría, añade
local_authy configura permisos según su guía oficial.
Buenas prácticas y manejo de errores #
- Inicializa el SDK desde el arranque de la app, antes de llamar métodos (revisa
apn.status). - Usa try/catch. El core puede lanzar errores de tipo
ApacuanaAPIError; el Notifier suele envolver resultados enApiResulto normalizar aMap. - Protege datos sensibles:
- Usa
encryptionKeydistinta en producción. - No loguees claves privadas ni certificados completos.
- Usa
- Biometría:
- Ofrece fallback al PIN si el dispositivo no soporta biometría.
- Modales del SDK:
ApacuanaProviderrenderizaApacuanaModalautomáticamente cuando el Notifier lo requiere (p. ej., liveness). No necesitas gestionarlo manualmente.
- Estados comunes:
NOT_INITIALIZED_ERROR: espera a queapn.status == ApacuanaServiceStatus.readyo corrige tuconfig.- Errores de red: revisa conectividad y
apiUrl.
Ejemplo de app #
Revisa la carpeta example/ del repositorio (si está disponible) para una app que:
- Inicializa el SDK con
ApacuanaProvider. - Presenta botones para invocar cada método y muestra resultados en JSON.
Ejemplo mínimo de botón para generar certificado:
ElevatedButton(
onPressed: () async {
final res = await ApacuanaProvider.of(context).generateCert(pin: '1234');
if (res.success) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Cert OK')),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: ${res.message}')),
);
}
},
child: const Text('Generar certificado'),
)
Licencia #
Este proyecto se distribuye bajo la licencia MIT (ver archivo LICENSE en la raíz del paquete).