apacuana_sdk_flutter 0.6.8 copy "apacuana_sdk_flutter: ^0.6.8" to clipboard
apacuana_sdk_flutter: ^0.6.8 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 #

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 un ApacuanaNotifier en 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 apiUrl y apiKey existen y no están vacíos: notifier.initCore(config).
  • Si customerId está 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: ApacuanaSuccess con data contendo token/userData.

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 ready y customerId. IMPORTANTE: el usuario debe estar previamente aprobado por tu backend (o por el proceso de verificación que uses) antes de intentar generar el certificado.

  • Descripción: Genera un par de claves en el dispositivo, crea un CSR, lo envía al core/backend y almacena localmente la clave privada y el certificado cifrados.

  • Recomendación de flujo eficiente:

    1. Antes de llamar a generateCert, consulta getCertStatus() y valida que el estado del certificado sea "Por generar". Si no está en ese estado, evita generar el certificado y muestra un mensaje al usuario o reintenta según tu lógica.
    2. Si getCertStatus() indica "Por generar", llama a generateCert(pin: ...).
  • Retorno: ApiResult con data: CertData (cert y certifiedId) cuando aplica.

Ejemplo recomendado:

// 1) Comprobar estado
final statusRes = await apn.getCertStatus();
if (statusRes['success'] == true) {
  final remoteState = (statusRes['data'] != null && statusRes['data']['state'] != null)
      ? statusRes['data']['state'].toString()
      : (statusRes['data'] != null && statusRes['data']['status'] != null)
          ? statusRes['data']['status'].toString()
          : '';

  if (remoteState.toLowerCase().contains('por generar')) {
    // 2) Generar certificado
    final res = await apn.generateCert(pin: '1234', enableSaveWithBiometrics: false);
    if (res.success) {
      // éxito
    } else {
      // manejar error
    }
  } else {
    // Usuario no está en estado "Por generar". Mostrar mensaje o actuar según tu flujo.
  }
} else {
  // No se pudo consultar el estado: manejar error/mostrar feedback
}

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 para ese customerId.

Ejemplo:

final hasCert = await apn.isCertificateInDevice();

getCertStatus #

  • Firma: Future<Map<String, dynamic>> getCertStatus()
  • Requiere: SDK ready y customerId.
  • Descripción: Pregunta al core el estado del certificado (normaliza la respuesta a Map encodable).
  • Retorno: Map con success, data, etc. El campo data suele contener información sobre el estado del proceso de certificación.

Estados posibles y su significado (valores mostrados como ejemplo — adapta a tu backend si usan otros textos):

  • Por verificar — El usuario ha iniciado el flujo pero aún no ha aportado la información/documentación necesaria para la verificación.
  • En revisión — La documentación o resultados están siendo revisados por el sistema.
  • Por generar — El usuario está aprobado y el certificado puede generarse: este es el estado en el que conviene invocar generateCert().
  • Vigente — El certificado fue emitido y está activo en el sistema.
  • Por revocar — Se ha solicitado revocación pero aún no se ha procesado.
  • Revocado — El certificado fue revocado y ya no es válido.
  • Verificado — Estado que puede indicar que la identidad fue verificada correctamente (a veces equivalente a Por generar).

Manejo sugerido por estado (ejemplo corto):

final status = await apn.getCertStatus();
if (status['success'] == true) {
  final data = status['data'] ?? {};
  final state = (data['state'] ?? data['status'] ?? data['phase'] ?? '').toString().toLowerCase();

  if (state.contains('por generar') || state.contains('ready') || state.contains('verified')) {
    // Generar certificado (el usuario está aprobado o listo)
  } else if (state.contains('por verificar') || state.contains('pending')) {
    // Indicar al usuario que complete la verificación
  } else if (state.contains('en revisión') || state.contains('review')) {
    // Mostrar estado de revisión y evitar generar certificado
  } else if (state.contains('vigente') || state.contains('issued')) {
    // Mostrar que ya tiene certificado vigente
  } else if (state.contains('por revocar') || state.contains('revoked') || state.contains('revocar')) {
    // Tratar la revocación o informar al usuario
  } else {
    // Manejo por defecto
  }
} else {
  // Error consultando estado
}

Recomendación:

  • Implementa comparaciones tolerantes (tolower/contains) y un mapeo local de valores del backend a los estados semánticos anteriores para evitar romper la UI si cambian las etiquetas exactas en el servidor.
  • Evita generar certificados hasta que el estado indique explícitamente que está "Por generar" o su equivalente.

getCertTypes #

  • Firma: Future<dynamic> getCertTypes()
  • Requiere: público (no requiere customerId).
  • Descripción: Devuelve la lista de tipos de certificado disponibles en el backend/core. Cada elemento suele contener un id y metadatos sobre requisitos para ese tipo de certificado.
  • Importancia: Antes de crear un usuario con createApacuanaUser, necesitas conocer qué tipo de certificado usará esa persona. getCertTypes() te permite obtener el id del tipo que luego debes incluir en el payload de createApacuanaUser (por ejemplo, como typeuser ).

Retorno: una lista/estructura con los tipos. La forma exacta depende del core, por ejemplo:

[
  { "id": "1", "name": "Certificado A", "requires": [ ... ] },
  { "id": "2", "name": "Certificado B", "requires": [ ... ] }
]

Ejemplo práctico (obtener tipos y usar el id al crear usuario):

// 1) Obtener tipos disponibles
final typesRes = await apn.getCertTypes();
// typesRes puede ser List<dynamic> o un wrapper; adapta según tu core
final List<dynamic> types = typesRes is List ? typesRes : (typesRes['data'] ?? []);

// 2) Buscar el tipo deseado (por nombre o criterio)
final desired = types.firstWhere(
  (t) => (t['name'] ?? '').toString().toLowerCase().contains('certificado a'),
  orElse: () => null,
);
if (desired == null) {
  // manejar: tipo no disponible
}
final typeuser = desired['id'].toString();

// 3) Crear payload de usuario incluyendo el tipo de certificado
final payload = {
  'kinddoc': 'V',
  'doc': '12345678',
  'email': 'usuario@correo.com',
  'name': 'Nombre',
  'lastname': 'Apellido',
  'phone': 4141234567,
  
  'typeuser': typeuser,
};

final res = await apn.createApacuanaUser(payload);

Notas:

  • El nombre del campo (typeuser en el ejemplo) y la estructura del resultado de getCertTypes() pueden variar. Revisa la respuesta real y adapta las claves.
  • Obtener y validar el tipo antes de crear el usuario evita errores y asegura que el usuario quede configurado con el certificado correcto desde el inicio.
  • Tras crear el usuario, tu backend puede devolver el estado inicial del certificado (ver getCertStatus) para continuar el flujo.

getRequerimentsByTypeUser #

  • Firma: Future<dynamic> getRequerimentsByTypeUser({ required int type })
  • Requiere: público.
  • Parámetros:
    • type (int): tipo de usuario.
  • Descripción: Devuelve un listado de los requisitos (documentos, comprobantes, imágenes, etc.) que el backend necesita para crear un usuario de ese tipo. Cada requisito normalmente incluye un id, name y metadatos sobre el tipo de archivo esperado.
  • Uso clave: al crear el usuario con createApacuanaUser, debes adjuntar los archivos requeridos usando claves con el formato file-{id} donde {id} es el id que devuelve getRequerimentsByTypeUser para cada documento. El valor debe ser un MultipartFile (por ejemplo dio.MultipartFile).

Retorno: lista/estructura con los requisitos. Ejemplo aproximado:

[
  { "id": 1, "name": "Cédula de identidad" },
  { "id": 2, "name": "Rif" },
]

Ejemplo práctico completo (obtener requisitos y crear usuario adjuntando archivos):

import 'package:dio/dio.dart' show MultipartFile;

// 1) Obtener requisitos para el tipo de usuario
final reqsRes = await apn.getRequerimentsByTypeUser(type: 1);
final List<dynamic> reqs = reqsRes is List ? reqsRes : (reqsRes['data'] ?? []);

// 2) Preparar payload básico del usuario
final payload = {
  'kinddoc': 'V',
  'doc': '12345678',
  'email': 'usuario@correo.com',
  'name': 'Nombre',
  'lastname': 'Apellido',
  'phone': 4141234567,
  // puedes incluir campos adicionales que tu backend requiera
};

// 3) Adjuntar archivos según requisitos: cada archivo debe usar la clave 'file-{id}'
final Map<String, dynamic> filesToUpload = {};
for (final r in reqs) {
  final reqId = r['id']?.toString();
  if (reqId == null) continue;

  // En tu app obtén la ruta del archivo correspondiente al requisito (picker, cámara, etc.)
  final localPath = await pickFilePathForRequirement(r);
  if (localPath == null) continue; // usuario no proporcionó ese archivo

  final multipart = await MultipartFile.fromFile(localPath, filename: localPath.split('/').last);
  filesToUpload['file-$reqId'] = multipart;
}

// 4) Combinar payload y archivos en el body que envía createApacuanaUser
final combined = {...payload, ...filesToUpload};

// 5) Llamada para crear usuario
final res = await apn.createApacuanaUser(combined);

// Manejo del resultado
if (res != null && (res['success'] == true || (res is Map && res['success'] == true))) {
  // Usuario creado
} else {
  // Error
}

Notas y recomendaciones:

  • pickFilePathForRequirement es una función de ejemplo: implementa tu flujo para que el usuario seleccione/ capture cada documento requerido.
  • Asegúrate de validar los tipos esperados y el tamaño máximo antes de crear MultipartFile.
  • Dependiendo createApacuanaUser puede esperar un FormData/multipart en lugar de un simple Map; el ejemplo usa dio.MultipartFile que es compatible con FormData.
  • Verifica la estructura concreta que tu core espera (nombres de campos, nombre exacto file-{id}, etc.) y adapta según sea necesario.
  • Este enfoque garantiza que cada archivo se asocie al requisito correcto mediante su id, facilitando el proceso de validación en backend.

addSigner #

  • Firma: Future<dynamic> addSigner(Map<String, dynamic> signer)
  • Requiere: customerId.
  • Descripción: Esta función permite agregar un firmante a un documento. Al ejecutarse correctamente, retorna un id (o signatureId) que debe ser usado posteriormente en la función signDocument para firmar el documento.
  • Parámetros (todos obligatorios):
    • doc (String): cédula del usuario (identificación). Ejemplo: '12345678'.
    • name (String): nombre del usuario. Ejemplo: 'Juan'.
    • typedoc (String): tipo de cédula, debe ser uno de los siguientes valores: 'V', 'E', 'P'.
    • file (MultipartFile): documento a firmar (por ejemplo, PDF o imagen). Usa dio.MultipartFile.
    • signature (List<Map<String, dynamic>>): array de mapas, cada uno representa una posición de firma y debe contener:
      • x (double): posición horizontal de la firma, valor porcentual entre 0 y 1 (0.0 = extremo izquierdo, 1.0 = extremo derecho). Ejemplo: 0.3 equivale al 30% desde la izquierda.
      • y (double): posición vertical de la firma, valor porcentual entre 0 y 1 (0.0 = parte superior, 1.0 = parte inferior). Ejemplo: 0.4 equivale al 40% desde arriba.
      • page (int): número de página donde se colocará la firma (comenzando en 1).

Nota: El punto (0,0) corresponde a la esquina superior izquierda de la hoja. Los valores de x y y son proporcionales al ancho y alto de la página respectivamente.

Ejemplo de uso

import 'package:dio/dio.dart';

final file = await MultipartFile.fromFile('/ruta/al/documento.pdf', filename: 'documento.pdf');
final signerPayload = {
  'doc': '12345678',
  'name': 'Juan',
  'typedoc': 'V',
  'file': file,
  'signature': [
    {
      'x': 0.3, // 30% desde la izquierda
      'y': 0.4, // 40% desde arriba
      'page': 1,
    },
    // Puedes agregar más posiciones si el documento requiere varias firmas
  ],
};

final addRes = await apn.addSigner(signerPayload);

// Extraer signatureId del resultado, cubriendo los posibles formatos de respuesta
final String signatureId = (() {
  try {
    final dyn = addRes as dynamic;
    final d = dyn.data ?? dyn['data'];
    if (d is Map) {
      return (d['signatureId'] ?? d['id'] ?? d['signature']?['id'] ?? '').toString();
    }
  } catch (_) {}
  if (addRes is Map<String, dynamic>) {
    final d = addRes['data'];
    if (d is Map) {
      return (d['signatureId'] ?? d['id'] ?? d['signature']?['id'] ?? '').toString();
    }
  }
  return '';
})();

if (signatureId.isNotEmpty) {
  // Usa signatureId en signDocument
  print('signatureId: ' + signatureId);
} else {
  // Manejar error o respuesta inesperada
  print('No se pudo obtener el signatureId');
}

getDocsByCustomer #

  • Firma: Future<dynamic> getDocsByCustomer({ required int page, required int size, int? status })
  • Requiere: customerId.
  • Parámetros:
    • page (int) > 0
    • size (int) > 0
    • status (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.
  • Flujo recomendado: Antes de llamar a esta función, debes haber agregado el firmante usando addSigner. Esta función retorna directamente un campo signature (mapa) que contiene tanto el id como las posiciones (positions con x, y, page). Ese mismo map signature debe ser usado como parámetro en signDocument.
  • Descripción: Esta función aplica la firma electrónica sobre el documento previamente registrado. El parámetro signature debe ser el mismo map retornado por addSigner. El parámetro pin es la contraseña que el usuario definió al generar su certificado electrónico.
  • Parámetros:
    • signature (Map):
      • id (String): id de la firma/documento, retornado dentro del campo signature de addSigner.
      • positions (List
      • document (MultipartFile o String base64, opcional): documento a firmar (solo si el backend lo requiere en este paso).
    • pin (String): contraseña definida por el usuario al generar el certificado.
    • enableBiometric (bool): si es true y el usuario tiene biometría habilitada, solicitará autenticación biométrica.
  • Retorno: ApiResult con la respuesta del core.

Importante:

  1. Siempre debes llamar primero a addSigner para registrar el firmante. Esta función retorna un campo signature que contiene el id y las posiciones.
  2. Usa ese mismo map signature como parámetro en signDocument.
  3. El campo document solo es necesario si el backend lo requiere en este paso (en la mayoría de los flujos, el documento ya fue subido en addSigner).
  4. El pin es el password que el usuario eligió al generar su certificado electrónico.

Ejemplo de flujo completo

import 'package:dio/dio.dart';

// 1. Agregar firmante y obtener el map signature
final file = await MultipartFile.fromFile('/ruta/al/documento.pdf', filename: 'documento.pdf');
final signerPayload = {
  'doc': '12345678',
  'name': 'Juan',
  'typedoc': 'V',
  'file': file,
  'signature': [
    {'x': 0.3, 'y': 0.4, 'page': 1},
  ],
};
final addRes = await apn.addSigner(signerPayload);
final Map<String, dynamic> signature = addRes['signature'];

// 2. Firmar el documento usando el map signature y el pin del usuario
final pin = '1234'; // PIN definido por el usuario al generar el certificado
final res = await apn.signDocument(signature: signature, pin: pin);
if (res.success) {
  print('Documento firmado correctamente');
} else {
  print('Error al firmar: \\${res.message}');
}

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.
  • Descripción: Crea (o actualiza) un usuario en Apacuana. Al crear correctamente, la respuesta incluye un identificador del usuario (id). Ese customerId es el valor que podrás usar en la configuración del SDK (customerId en config) para inicializar Apacuana asociado a ese usuario.

Campos obligatorios mínimos:

  • kinddoc (String)
  • doc (String o numérico)
  • email (String)

Comportamiento:

  • Si el usuario no existía, la función crea el usuario y devuelve un id que es el customerId asociado.
  • Si el usuario ya existe y vuelves a llamar la función con el mismo identificador (por ejemplo doc/kinddoc), la llamada debe comportarse como una actualización: se modifican los campos enviados en el payload sin crear un duplicado.

Subida de documentos:

  • Para adjuntar documentos solicitados por getRequerimentsByTypeUser, incluye en el payload campos con nombre file-{id} donde {id} es el id del requisito devuelto por getRequerimentsByTypeUser. El valor debe ser un MultipartFile (dio.MultipartFile) y el request debe enviarse como multipart/FormData.

Reglas específicas:

  • Si el tipo de usuario corresponde a "Profesional titulado" (según el typeuser), entonces el campo occupation es obligatorio y debe ser enviado.

Campos adicionales (lista de los campos aceptados por la función):

{
  email,
  typeuser,
  name,
  lastname,
  kinddoc,
  doc,
  birthdate,
  kindrif,
  gender,
  rif,
  phone,
  kindphone,
  state,
  municipality,
  parish,
  postalcode,
  address,
  fiscaladdress,
  fiscalkindphone,
  fiscalphone,
  occupation,          // obligatorio para Profesional titulado
  degree,
  university,
  graduationyear,
  collegiatenumber,
  collegiateyear,
  companyname,
  companykindrif,
  companyrif,
  companystate,
  companymunicipality,
  companyparish,
  companyaddress,
  companykindphone,
  companyphone,
  companypostalcode,
  companywebpage,
  companycommercialregister,
  companyregisterdate,
  companyregisternumber,
  companyconstitutiondate,
  companypublishdate,
  companyconstitutiondecree,
  companynumberdecree,
  positionprivate,
  departmentprivate,
  authorizedprivate,
  functionsprivate,
  publishprivate,
  issuedateprivate,
  kindphoneprivate,
  phoneprivate,
  positionpublic,
  departmentpublic,
  authorizedpublic,
  functionspublic,
  publishpublic,
  issuedatepublic,
  kindphonepublic,
  phonepublic,
  companyid
}

Ejemplo práctico completo (crear usuario con archivos):

import 'package:dio/dio.dart' show MultipartFile, FormData;

// 1) Obtener tipos y requisitos (opcional)
final types = await apn.getCertTypes();
final reqs = await apn.getRequerimentsByTypeUser(type: selectedType);

// 2) Preparar campos del usuario
final payload = {
  'kinddoc': 'V',
  'doc': '12345678',
  'email': 'usuario@correo.com',
  'name': 'Nombre',
  'lastname': 'Apellido',
  'typeuser': selectedType,
  // si selectedType corresponde a Profesional titulado, asegúrate de incluir 'occupation'
};

// 3) Adjuntar archivos según `reqs` (cada archivo con clave 'file-{id}')
final Map<String, dynamic> formMap = {};
formMap.addAll(payload);
for (final r in reqs) {
  final reqId = r['id']?.toString();
  if (reqId == null) continue;
  final localPath = await pickFilePathForRequirement(r); // implementa picker
  if (localPath == null) continue;
  final multipart = await MultipartFile.fromFile(localPath, filename: localPath.split('/').last);
  formMap['file-$reqId'] = multipart;
}

// 4) Enviar como FormData si el core lo requiere
final formData = FormData.fromMap(formMap);
final res = await apn.createApacuanaUser(formData.fields.asMap()); // adapta según la implementación del wrapper/core

// 5) Manejo de la respuesta: guardar customerId si se crea
if (res != null) {
  final createdId = res['data'] != null ? res['data']['id'] ?? res['data']['customerId'] : null;
  if (createdId != null) {
    // Guarda createdId como customerId en tu configuración para inicializar Apacuana
  }
}

Notas finales:

  • Revisa la especificación para confirmar el nombre exacto del campo donde devuelve el id (puede ser id, customerId, userId, etc.).
  • Valida los requisitos y tipos de archivos antes de crear MultipartFile.
  • Al volver a llamar la función con los mismos datos de identidad (kinddoc + doc) se debe actualizar el usuario en lugar de crear uno nuevo.

uploadSignatureVariant #

  • Firma: Future<dynamic> uploadSignatureVariant({ required MultipartFile file })
  • Requiere: customerId.
  • Parámetros:
    • file (MultipartFile de dio): 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 (p. ej. Face Liveness que utiliza la cámara en WebView), debes añadir permisos en cada plataforma y, en Android, manejar permisos en tiempo de ejecución y/o la petición de permisos desde el WebView nativo.

A continuación se indican las acciones mínimas y recomendaciones por plataforma.

Android #

Archivos a editar:

  • android/app/src/main/AndroidManifest.xml

Permisos necesarios (añade dentro de la etiqueta <manifest>):

<!-- Permite acceso a internet (necesario para WebView) -->
<uses-permission android:name="android.permission.INTERNET" />

<!-- Permiso de cámara para liveness -->
<uses-permission android:name="android.permission.CAMERA" />

<!-- Indica que la app puede usar cámara, pero no la hace obligatoria -->
<uses-feature android:name="android.hardware.camera" android:required="false" />

Notas importantes para Android:

  • Además del AndroidManifest.xml, debes solicitar el permiso de cámara en tiempo de ejecución antes de abrir el WebView que usará getUserMedia. Recomendamos usar el paquete permission_handler:
import 'package:permission_handler/permission_handler.dart';

final status = await Permission.camera.request();
if (!status.isGranted) {
  // Maneja rechazo: muestra error o pide al usuario que otorgue permiso en Ajustes
}
  • El contenido web que use getUserMedia (WebRTC) también solicita permisos dentro del WebView. Para garantizar que el WebView acepte esas peticiones debes usar una implementación que exponga onPermissionRequest (por ejemplo flutter_inappwebview) y conceder recursos solicitados:
InAppWebView(
  androidOnPermissionRequest: (controller, origin, resources) async {
    return PermissionRequestResponse(
      resources: resources,
      action: PermissionResponseAction.GRANT, // concede recursos (ver versión del plugin)
    );
  },
  // ...
)
  • Prueba en dispositivo físico. Emuladores pueden no exponer cámara o comportarse de forma distinta.
  • Usa adb logcat para depurar errores del WebView y getUserMedia.

iOS #

Archivo a editar:

  • ios/Runner/Info.plist

Añade las descripciones de uso (claves y textos explicativos) para que iOS muestre la alerta al solicitar permisos:

<key>NSCameraUsageDescription</key>
<string>Usamos la cámara para verificar la vivacidad (liveness) en el flujo de verificación facial.</string>

<key>NSPhotoLibraryAddUsageDescription</key>
<string>Se requiere acceso a la galería si se desea subir una imagen desde Fotos.</string>

<key>NSPhotoLibraryUsageDescription</key>
<string>Se requiere acceso a la galería si se desea subir una imagen desde Fotos.</string>

Notas importantes para iOS:

  • Con solo añadir estas claves, el sistema mostrará la alerta de permiso cuando el WebView solicite acceso a la cámara.
  • Si usas webview_flutter o flutter_inappwebview, getUserMedia generalmente funciona en iOS siempre que la página lo solicite y la clave NSCameraUsageDescription esté presente.
  • Prueba en dispositivo físico (simulador no siempre proporciona la cámara real).

Web #

  • Si mantienes la opción Web que abre una pestaña (WebViewLiveness), la cámara dependerá del navegador y sus permisos. Informa al usuario que debe permitir el acceso a la cámara en el prompt del navegador.
  • Asegúrate de servir la página sobre HTTPS (requisito para getUserMedia en la mayoría de navegadores).

Resumen de pasos recomendados (rápido) #

  1. Añadir permisos en AndroidManifest.xml y Info.plist según corresponda.
  2. Antes de abrir el WebView en Android solicita Permission.camera en tiempo de ejecución.
  3. Usar flutter_inappwebview (o la API de webview_flutter que permita manejar requests de permisos) y conceder androidOnPermissionRequest para recursos como VIDEO_CAPTURE.
  4. Probar en dispositivos reales y revisar adb logcat/Xcode logs si algo falla.

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 en ApiResult o normalizar a Map.
  • Protege datos sensibles:
    • Usa encryptionKey distinta en producción.
    • No loguees claves privadas ni certificados completos.
  • Biometría:
    • Ofrece fallback al PIN si el dispositivo no soporta biometría.
  • Modales del SDK:
    • ApacuanaProvider renderiza ApacuanaModal automáticamente cuando el Notifier lo requiere (p. ej., liveness). No necesitas gestionarlo manualmente.
  • Estados comunes:
    • NOT_INITIALIZED_ERROR: espera a que apn.status == ApacuanaServiceStatus.ready o corrige tu config.
    • 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).

0
likes
85
points
80
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Apacuana SDK Core proporciona una interfaz para interactuar con los servicios de Apacuana.

License

unknown (license)

Dependencies

apacuana_sdk_core_dart, asn1lib, basic_utils, convert, crypto, dio, encrypt, face_liveness_detector, flutter, flutter_inappwebview, flutter_secure_storage, local_auth, path_provider, permission_handler, pointycastle, provider, url_launcher, webview_flutter

More

Packages that depend on apacuana_sdk_flutter