universal_beacon_sdk 1.0.2 
universal_beacon_sdk: ^1.0.2 copied to clipboard
SDK genérico y robusto para detección de beacons BLE. Ideal para control de acceso, sistemas de asistencia, marketing de proximidad, automatización IoT, tracking de assets y aplicaciones empresariales.
Universal Beacon SDK #
SDK genérico y robusto para detección de beacons BLE con detección ultra-rápida (100-300ms) y optimizaciones para sistemas empresariales. Ideal para control de acceso, asistencia, marketing de proximidad, IoT, tracking de assets y aplicaciones de salud.
⚡ Destacados #
- Detección Ultra-Rápida: De 100ms a 5s según tu caso de uso
 - 4 Configuraciones Optimizadas: ultraFast, balanced, enterprise, batterySaver
 - Escalabilidad: Maneja 100+ beacons con cache inteligente
 - Configuración Total: Control completo de todos los parámetros
 - Producción Lista: Usado en sistemas empresariales reales
 
🚀 Características Principales #
- ⚡ Detección Optimizada: 100-300ms en modo ultraFast, configurable hasta 5s
 - 🎯 4 Presets Listos: ultraFast, accessControl, enterprise, batterySaver
 - 🏢 Escalable: Cache de parsing, priorización, throttling para 100+ beacons
 - 🔧 100% Configurable: Control total de scanMode, intervalos, filtros, optimizaciones
 - 📡 Detección Universal: iBeacons, Eddystone, formatos personalizados
 - 🔋 Monitoreo de Batería: Lectura automática para beacons HOLYIOT (Normal e iBeacon)
 - 🎨 Gestión de Registros: Sistema completo para beacons conocidos
 - 📊 Eventos en Tiempo Real: Streams reactivos para entrada/salida de rango
 - 📏 Cálculo de Distancia: Algoritmos precisos basados en RSSI
 - 🔍 Logging Avanzado: Sistema configurable para debugging
 - 💾 Persistencia: Almacenamiento local con SharedPreferences
 - 📱 Multiplataforma: Android e iOS
 - ⚡ Eficiencia Energética: Modo batterySaver con consumo mínimo
 
📦 Instalación #
Añade el SDK a tu pubspec.yaml:
dependencies:
  universal_beacon_sdk: ^1.0.0
Luego ejecuta:
flutter pub get
⚡ Inicio Rápido #
import 'package:universal_beacon_sdk/universal_beacon_sdk.dart';
// 1. Crear instancia del scanner
final scanner = UniversalBeaconScanner();
// 2. Inicializar con configuración ultra-rápida
await scanner.initialize(
  config: BeaconConfig.ultraFast(), // 100-300ms detección
  registeredBeacons: [
    BeaconRegistry(
      id: 'beacon_001',
      uuid: 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
      displayName: 'Puerta Principal',
    ),
  ],
);
// 3. Escuchar eventos
scanner.beaconEvents.listen((event) {
  if (event.type == BeaconEventType.detected) {
    print('¡Beacon detectado en ${event.timestamp}ms!');
    print('RSSI: ${event.device.rssi} dBm');
    print('Distancia: ${event.device.distance}m');
  }
});
// 4. Iniciar escaneo
await scanner.startScanning();
🎯 Configuraciones Optimizadas #
⚡ Ultra Rápido (100-300ms) #
Ideal para control de acceso, puertas, torniquetes
await scanner.initialize(
  config: BeaconConfig.ultraFast(),
);
⚖️ Balanceado (500ms) #
Para asistencia, uso general
await scanner.initialize(
  config: BeaconConfig.accessControl(),
);
🏢 Empresarial (100+ beacons) #
Hospitales, almacenes, campus grandes
await scanner.initialize(
  config: BeaconConfig.enterprise(),
);
� Ahorro de Batería #
Apps de larga duración, tracking pasivo
await scanner.initialize(
  config: BeaconConfig.batterySaver(),
);
🔧 Configuración Personalizada #
Control total de todos los parámetros
await scanner.initialize(
  config: BeaconConfig(
    // Velocidad BLE
    scanMode: ScanMode.lowLatency,  // opportunistic, lowPower, balanced, lowLatency
    
    // Intervalos (cualquier valor en ms)
    scanInterval: 250,              // ← Puedes poner 100, 250, 500, 1000, etc.
    minUpdateInterval: 50,          // ← Control por dispositivo
    streamThrottle: 200,            // ← Actualización de UI
    
    // Filtros
    minRssi: -85,                   // De -100 a 0
    maxDistance: 15.0,              // En metros
    deviceLostTimeout: 10,          // En segundos
    
    // Optimizaciones
    prioritizeRegisteredBeacons: true,   // Beacons conocidos primero
    immediateEventEmission: true,        // Eventos instantáneos
    enableParsingCache: true,            // Cache para reducir CPU 70%
    iBeaconOnly: false,                  // Solo iBeacons o todos
    
    // Debug
    logLevel: 2,                    // 0=errors, 1=warnings, 2=info, 3=verbose
  ),
);
📊 Tabla Comparativa de Presets #
Elige el preset correcto según tus necesidades:
| Preset | Detección | Batería | Beacons | Uso Ideal | Trade-offs | 
|---|---|---|---|---|---|
| ultraFast ⚡ | 100-300ms | Alta 🔴 | <50 | Control de acceso, puertas, torniquetes donde el usuario espera respuesta inmediata | Consume más batería, no recomendado para apps que corren todo el día | 
| accessControl ⚖️ | 500ms | Media 🟡 | <100 | Asistencia escolar/laboral, check-in/check-out, monitoreo de presencia | Balance óptimo para mayoría de casos | 
| enterprise 🏢 | 1-1.5s | Media 🟡 | 100+ | Hospitales, almacenes, campus con muchos beacons simultáneos | Detección más lenta pero maneja gran escala | 
| batterySaver 🔋 | 3-7s | Mínima 🟢 | Cualquiera | Tracking de assets a largo plazo, monitoreo pasivo, apps que corren días | Detección muy lenta, no para interacción | 
🎯 ¿Cuál Preset Usar? #
Usa ultraFast si:
- ✅ Necesitas respuesta inmediata (<500ms)
 - ✅ Usuario interactúa activamente (ej: abrir puerta)
 - ✅ La app solo escanea cuando se necesita
 - ❌ NO si la app corre todo el día
 
Usa accessControl si:
- ✅ Necesitas buen balance velocidad/batería
 - ✅ Detección de 500ms es aceptable
 - ✅ Caso de uso típico (asistencia, check-in)
 - ✅ No estás seguro cuál elegir → Empieza aquí
 
Usa enterprise si:
- ✅ Tienes 50+ beacons en el área
 - ✅ Rendimiento es crítico con muchos dispositivos
 - ✅ Necesitas cache para reducir CPU
 - ❌ NO si tienes pocos beacons (usa accessControl)
 
Usa batterySaver si:
- ✅ La app debe correr días sin recargar
 - ✅ Detección no es time-critical
 - ✅ Solo necesitas ubicación periódica
 - ❌ NO si necesitas interacción en tiempo real
 
🎯 Casos de Uso #
Control de Acceso #
import 'package:universal_beacon_sdk/universal_beacon_sdk.dart';
final scanner = UniversalBeaconScanner();
// Configuración ultra-rápida para puertas
await scanner.initialize(
  config: BeaconConfig.ultraFast(), // 100-300ms
  registeredBeacons: [
    BeaconRegistry(
      id: 'employee_001',
      uuid: 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
      displayName: 'Juan Pérez',
      metadata: {'department': 'IT', 'level': 'admin'},
    ),
  ],
);
// Escuchar eventos de acceso
scanner.beaconEvents.listen((event) {
  if (event.type == BeaconEventType.detected) {
    print('✅ Acceso autorizado: ${event.device.displayName}');
    openDoor(); // Puerta abre en ~200ms
  }
});
await scanner.startScanning();
Sistema de Asistencia #
// Configuración balanceada
await scanner.initialize(
  config: BeaconConfig.accessControl(),
  registeredBeacons: students,
);
scanner.beaconEvents.listen((event) {
  if (event.type == BeaconEventType.detected) {
    registerAttendance(event.device);
  }
  if (event.type == BeaconEventType.lost) {
    markExit(event.device);
  }
});
    // Marcar asistencia
  }
});
Marketing de Proximidad #
// Configuración para marketing
await scanner.initialize(
  config: ScannerConfig.proximityMarketing,
);
// Registrar productos
await scanner.registerBeacon(BeaconRegistry(
  id: 'product_001',
  uuid: 'F7826DA6-4FA2-4E98-8024-BC5B71E0893E',
  displayName: 'Promoción Especial',
  category: 'marketing',
  metadata: {'campaign': 'summer_sale', 'discount': '20%'},
  registeredAt: DateTime.now(),
));
// Activar ofertas por proximidad
scanner.events.listen((event) {
  if (event.type == BeaconEventType.registeredEntered) {
    final discount = event.registry?.metadata['discount'];
    print('¡Oferta especial: $discount de descuento!');
    // Mostrar oferta
  }
});
🔧 API Principal #
Inicialización #
final scanner = UniversalBeaconScanner();
// Inicialización básica
await scanner.initialize();
// Inicialización con configuración
await scanner.initialize(
  config: BeaconConfig.balanced(),
  registeredBeacons: [/* lista de beacons */],
);
// Configuraciones predefinidas
await scanner.initialize(config: ScannerConfig.accessControl);
await scanner.initialize(config: ScannerConfig.attendance);
await scanner.initialize(config: ScannerConfig.proximityMarketing);
Configuración Personalizada #
final customConfig = BeaconConfig(
  scanInterval: 1000,
  minRssi: -80,
  maxDistance: 25.0,
  deviceLostTimeout: 10,
  iBeaconOnly: false,
  enableLogging: true,
  logLevel: 2,
  allowedUuids: ['FDA50693-A4E2-4FB1-AFCF-C6EB07647825'],
);
await scanner.initialize(config: customConfig);
🔋 Monitoreo de Batería #
El SDK incluye detección automática de nivel de batería para beacons HOLYIOT NRF52810. Funciona en ambos modos (Normal e iBeacon) sin configuración adicional.
Modos Soportados #
1. Modo Normal/Generic BLE
Beacons transmiten serviceData con UUID personalizado 0x5242:
Datos: 41 63 ce 07 42 82 e8 59 03 02 06 00 00
       ↑  ↑
       |  byte[1] = 0x63 = 99% batería
2. Modo iBeacon
Beacons transmiten manufacturerData con datos extendidos:
Datos: 06 00 01 09 20 ... [byte 25] [byte 26] [byte 27]
                          ↑        ↑        ↑
                          Posiciones de batería (prioridad: 25→26→27)
Propiedades Disponibles #
scanner.detectedDevices.listen((devices) {
  for (var device in devices) {
    // Verificar si tiene información de batería
    if (device.hasBatteryInfo) {
      // Nivel de batería (0-100%)
      print('Batería: ${device.batteryLevel}%');
      
      // Icono visual
      print('Estado: ${device.batteryIcon}'); // 🔋 o 🪫
      
      // Color para UI ('green', 'orange', 'red')
      print('Color: ${device.batteryColor}');
      
      // Timestamp de última lectura
      print('Leído: ${device.batteryLastRead}');
      
      // Alertas automáticas
      if (device.isBatteryCritical) {
        print('⚠️ CRÍTICO: < 10% - Reemplazar inmediatamente');
      } else if (device.isBatteryLow) {
        print('⚡ BAJO: < 20% - Programar reemplazo');
      }
    }
  }
});
Ejemplo Completo de Monitoreo #
// Sistema de alertas de batería
void monitorBatteryStatus() {
  scanner.detectedDevices.listen((devices) {
    for (var device in devices.where((d) => d.hasBatteryInfo)) {
      final batteryInfo = {
        'name': device.displayName,
        'level': device.batteryLevel,
        'icon': device.batteryIcon,
        'timestamp': device.batteryLastRead,
      };
      
      // Log de estado
      print('${batteryInfo['icon']} ${batteryInfo['name']}: ${batteryInfo['level']}%');
      
      // Alertas críticas
      if (device.isBatteryCritical) {
        sendCriticalAlert(device);
        scheduleImmediateReplacement(device);
      }
      
      // Alertas preventivas
      else if (device.isBatteryLow) {
        sendLowBatteryWarning(device);
        addToMaintenanceQueue(device);
      }
    }
  });
}
UI de Batería #
// Widget de batería con colores
Widget buildBatteryIndicator(BeaconDevice device) {
  if (!device.hasBatteryInfo) return SizedBox.shrink();
  
  Color getBatteryColor() {
    switch (device.batteryColor) {
      case 'green': return Colors.green;
      case 'orange': return Colors.orange;
      case 'red': return Colors.red;
      default: return Colors.grey;
    }
  }
  
  return Container(
    padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
    decoration: BoxDecoration(
      color: getBatteryColor().withOpacity(0.2),
      borderRadius: BorderRadius.circular(12),
      border: Border.all(color: getBatteryColor(), width: 1.5),
    ),
    child: Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Text(device.batteryIcon, style: TextStyle(fontSize: 12)),
        SizedBox(width: 4),
        Text(
          '${device.batteryLevel}%',
          style: TextStyle(
            color: getBatteryColor(),
            fontWeight: FontWeight.bold,
            fontSize: 12,
          ),
        ),
      ],
    ),
  );
}
Compatibilidad #
- ✅ HOLYIOT NRF52810: Ambos modos (Normal e iBeacon)
 - ✅ Battery Service Estándar: UUID 0x180F (BLE estándar)
 - ✅ Backward Compatible: Beacons sin batería siguen funcionando
 - ✅ Automático: Sin configuración manual requerida
 - ✅ Performance: Parsing optimizado, sin impacto en velocidad
 
Gestión de Beacons #
// Registrar beacon
final beacon = BeaconRegistry(
  id: 'unique_id',
  uuid: 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
  displayName: 'Mi Beacon',
  description: 'Descripción del beacon',
  category: 'general',
  metadata: {'custom': 'data'},
  registeredAt: DateTime.now(),
);
await scanner.registerBeacon(beacon);
// Obtener beacons registrados
final registeredBeacons = scanner.registeredBeacons;
// Buscar beacons
final searchResults = scanner.registryManager.searchBeacons('Holy');
Escaneo y Eventos #
// Iniciar escaneo
await scanner.startScanning();
// Escuchar todos los eventos
scanner.events.listen((event) {
  switch (event.type) {
    case BeaconEventType.detected:
      print('Nuevo beacon: ${event.device.name}');
      break;
    case BeaconEventType.registeredEntered:
      print('Beacon registrado entró: ${event.device.name}');
      break;
    case BeaconEventType.lost:
      print('Beacon perdido: ${event.device.name}');
      break;
  }
});
// Escuchar lista de dispositivos
scanner.devices.listen((devices) {
  print('Dispositivos detectados: ${devices.length}');
  for (final device in devices) {
    print('- ${device.name}: ${device.estimatedDistance?.toStringAsFixed(1)}m');
  }
});
// Escuchar estado del scanner
scanner.status.listen((status) {
  print('Estado: ${status.displayName}');
});
// Detener escaneo
await scanner.stopScanning();
📡 Los 3 Streams Principales #
El SDK expone 3 streams que cubren todos los casos de uso:
1️⃣ beaconEvents - Eventos Individuales en Tiempo Real #
Cuándo usarlo: Necesitas reaccionar inmediatamente a cada beacon (ej: abrir puerta)
scanner.beaconEvents.listen((event) {
  // Cada evento contiene:
  // - event.type: BeaconEventType (detected, updated, lost)
  // - event.device: BeaconDevice con toda la info
  // - event.timestamp: DateTime del evento
  
  switch (event.type) {
    case BeaconEventType.detected:
      // Primera vez que se detecta el beacon
      print('🆕 Nuevo: ${event.device.displayName}');
      openDoor(); // Acción inmediata
      break;
      
    case BeaconEventType.updated:
      // Beacon ya conocido, datos actualizados (RSSI, distancia)
      print('🔄 Actualizado: ${event.device.rssi} dBm');
      break;
      
    case BeaconEventType.lost:
      // Beacon salió del rango o se perdió señal
      print('❌ Perdido: ${event.device.displayName}');
      closeDoor();
      break;
  }
});
✅ Casos de uso:
- Control de acceso (abrir puerta cuando se detecta)
 - Notificaciones push individuales
 - Triggers de acciones específicas
 - Logging detallado de eventos
 
❌ NO usar para:
- Actualizar UI de lista (usa 
detectedDevices) - Verificar estado general (usa 
status) 
2️⃣ detectedDevices - Lista Completa de Beacons Activos #
Cuándo usarlo: Necesitas mostrar lista en UI o procesar múltiples beacons
scanner.detectedDevices.listen((devices) {
  // devices es List<BeaconDevice>
  // Se actualiza según streamThrottle (default: 500ms)
  
  setState(() {
    _beaconsList = devices;
    _totalBeacons = devices.length;
    _registeredCount = devices.where((d) => d.isRegistered).length;
  });
  
  // Filtrar por distancia
  final nearbyBeacons = devices.where((d) => 
    d.distance != null && d.distance! < 5.0
  ).toList();
  
  // Filtrar por RSSI
  final strongSignal = devices.where((d) => d.rssi > -70).toList();
  
  // Ordenar por distancia
  devices.sort((a, b) => 
    (a.distance ?? 999).compareTo(b.distance ?? 999)
  );
});
✅ Casos de uso:
- ListView/GridView de beacons en UI
 - Dashboard con estadísticas
 - Mapa de beacons cercanos
 - Filtrado y ordenamiento de múltiples beacons
 
❌ NO usar para:
- Reaccionar a beacon específico (usa 
beaconEvents) - Solo verificar si está escaneando (usa 
status) 
3️⃣ status - Estado del Scanner #
Cuándo usarlo: Necesitas mostrar estado de conexión/escaneo en UI
scanner.status.listen((status) {
  // status es ScannerStatus enum
  
  switch (status) {
    case ScannerStatus.idle:
      // Scanner inicializado pero no escaneando
      print('⚪ Listo para escanear');
      _showStartButton();
      break;
      
    case ScannerStatus.scanning:
      // Escaneando activamente
      print('🔵 Escaneando...');
      _showStopButton();
      _showLoadingIndicator();
      break;
      
    case ScannerStatus.stopped:
      // Escaneo detenido manualmente
      print('🔴 Detenido');
      break;
      
    case ScannerStatus.error:
      // Error (permisos, bluetooth off, etc)
      print('❌ Error');
      _showPermissionDialog();
      break;
  }
});
✅ Casos de uso:
- Mostrar botón Start/Stop
 - Indicador de loading
 - Mensaje de error de permisos/bluetooth
 - Color de estado en UI
 
❌ NO usar para:
- Información de beacons (usa otros streams)
 
📋 Ejemplo Completo con los 3 Streams #
class BeaconScreen extends StatefulWidget {
  @override
  _BeaconScreenState createState() => _BeaconScreenState();
}
class _BeaconScreenState extends State<BeaconScreen> {
  final scanner = UniversalBeaconScanner();
  
  List<BeaconDevice> _devices = [];
  ScannerStatus _status = ScannerStatus.idle;
  String? _lastEvent;
  
  @override
  void initState() {
    super.initState();
    _initScanner();
  }
  
  Future<void> _initScanner() async {
    await scanner.initialize(
      config: BeaconConfig.accessControl(),
      registeredBeacons: [
        BeaconRegistry(
          id: 'door_1',
          uuid: 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
          displayName: 'Puerta Principal',
        ),
      ],
    );
    
    // Stream 1: Eventos individuales para acciones inmediatas
    scanner.beaconEvents.listen((event) {
      if (event.type == BeaconEventType.detected && event.device.isRegistered) {
        _openDoor(event.device);
      }
      setState(() {
        _lastEvent = '${event.type.name}: ${event.device.displayName}';
      });
    });
    
    // Stream 2: Lista completa para UI
    scanner.detectedDevices.listen((devices) {
      setState(() {
        _devices = devices;
      });
    });
    
    // Stream 3: Estado para botones e indicadores
    scanner.status.listen((status) {
      setState(() {
        _status = status;
      });
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Beacon Scanner'),
        actions: [
          // Indicador de estado (Stream 3)
          Icon(
            _status == ScannerStatus.scanning 
              ? Icons.bluetooth_searching 
              : Icons.bluetooth,
            color: _status == ScannerStatus.error ? Colors.red : Colors.green,
          ),
        ],
      ),
      body: Column(
        children: [
          // Último evento (Stream 1)
          if (_lastEvent != null)
            Card(
              child: Padding(
                padding: EdgeInsets.all(8),
                child: Text(_lastEvent!),
              ),
            ),
          
          // Lista de beacons (Stream 2)
          Expanded(
            child: ListView.builder(
              itemCount: _devices.length,
              itemBuilder: (context, index) {
                final device = _devices[index];
                return ListTile(
                  leading: Icon(
                    device.isRegistered ? Icons.verified : Icons.bluetooth,
                    color: device.isRegistered ? Colors.green : Colors.grey,
                  ),
                  title: Text(device.displayName ?? 'Beacon'),
                  subtitle: Text('${device.distance?.toStringAsFixed(1)}m'),
                  trailing: Text('${device.rssi} dBm'),
                );
              },
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        // Botón basado en estado (Stream 3)
        onPressed: _status == ScannerStatus.scanning 
          ? scanner.stopScanning 
          : scanner.startScanning,
        child: Icon(
          _status == ScannerStatus.scanning ? Icons.stop : Icons.play_arrow,
        ),
      ),
    );
  }
  
  void _openDoor(BeaconDevice device) {
    print('✅ Abriendo puerta para: ${device.displayName}');
    // Tu lógica aquí
  }
  
  @override
  void dispose() {
    scanner.dispose();
    super.dispose();
  }
}
🎯 BeaconRegistry: ¿Cuándo Usar y Cuándo NO? #
✅ USA BeaconRegistry CUANDO: #
1. Necesitas identificar beacons específicos
// ✅ Control de acceso - sabes exactamente qué badges esperar
registeredBeacons: [
  BeaconRegistry(id: 'employee_123', uuid: '...', displayName: 'Juan'),
  BeaconRegistry(id: 'employee_456', uuid: '...', displayName: 'María'),
]
2. Quieres metadata adicional
// ✅ Guardar info del empleado con el beacon
BeaconRegistry(
  id: 'emp_001',
  uuid: 'FDA50693-...',
  metadata: {
    'cargo': 'Desarrollador',
    'departamento': 'IT',
    'nivel_acceso': 'admin',
  },
)
3. Necesitas filtrar eventos de beacons conocidos
// ✅ Solo reaccionar a beacons registrados
scanner.beaconEvents.listen((event) {
  if (event.device.isRegistered) {
    openDoor(); // Solo para empleados registrados
  }
});
4. Quieres priorizar detección de beacons importantes
// ✅ Con prioritizeRegisteredBeacons: true
// Los beacons registrados se detectan más rápido
config: BeaconConfig(
  prioritizeRegisteredBeacons: true, // Empleados conocidos primero
)
❌ NO USES BeaconRegistry CUANDO: #
1. Solo quieres descubrir beacons disponibles
// ❌ NO necesitas registrar nada
// ✅ Simplemente escanea y ve qué hay
await scanner.initialize(config: BeaconConfig.accessControl());
scanner.detectedDevices.listen((devices) {
  print('Encontré ${devices.length} beacons');
});
2. No sabes qué beacons esperar
// ❌ Mala idea: registrar todos los UUIDs posibles
// ✅ Mejor: escanea y filtra después por proximidad/RSSI
scanner.detectedDevices.listen((devices) {
  final nearby = devices.where((d) => d.distance! < 5).toList();
});
3. Solo necesitas métricas generales
// ❌ No necesitas identificación individual
// ✅ Solo cuenta cuántos beacons hay cerca
final count = devices.length;
final avgDistance = devices.map((d) => d.distance).average;
4. Quieres máxima flexibilidad
// ❌ Registrar beacons limita detección si usas filtros
// ✅ Sin registrar, detectas TODOS los beacons
config: BeaconConfig(
  iBeaconOnly: false, // Detecta todos los formatos
  allowedUuids: null, // No filtrar por UUID
)
🎯 Regla General: #
Usa BeaconRegistry si: Tienes una lista conocida de beacons Y necesitas tratarlos de forma especial (metadata, priorización, filtrado)
No uses BeaconRegistry si: Solo quieres descubrir/monitorear beacons genéricamente
📈 Logging y Debugging #
final logger = BeaconLogger();
// Configurar nivel de logging
logger.setLevel(3); // 0: Error, 1: Warning, 2: Info, 3: Debug
// Obtener historial de logs
final history = logger.getHistory(filterLevel: LogLevel.error);
// Exportar logs
final exportedLogs = logger.exportLogs(since: DateTime.now().subtract(Duration(hours: 1)));
// Estadísticas
final stats = logger.getStatistics();
🎛️ Configuraciones Avanzadas #
Por Plataforma #
final config = BeaconConfig(
  scanInterval: 1000,
  platformConfig: PlatformSpecificConfig(
    android: AndroidConfig(
      scanMode: 2, // Balanced
      useLowPowerMode: false,
    ),
    ios: IOSConfig(
      allowBackgroundScanning: true,
      restoreState: true,
    ),
  ),
);
Configuración Específica por Beacon #
final beacon = BeaconRegistry(
  id: 'special_beacon',
  uuid: 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
  displayName: 'Beacon Especial',
  config: BeaconSpecificConfig(
    notificationCooldown: 5,
    maxDistance: 10.0,
    minRssi: -70,
    trackHistory: true,
    customConfig: {'special': true},
  ),
  registeredAt: DateTime.now(),
);
💾 Persistencia y Exportación #
// Los registros se guardan automáticamente
// Exportar registros
final exported = scanner.registryManager.exportToJson();
// Importar registros
await scanner.registryManager.importFromJson(exported);
// Estadísticas
final stats = scanner.registryManager.getStatistics();
🧪 Configuraciones de Testing #
// Configuración para desarrollo
await scanner.initialize(config: ScannerConfig.debug);
// Configuración para demo
await scanner.initialize(config: ScannerConfig.demo);
📱 Casos de Uso Específicos #
IoT y Automatización #
scanner.events.listen((event) {
  if (event.device.category == 'sensor' && 
      event.type == BeaconEventType.registeredEntered) {
    // Activar automatización
    triggerIoTAction(event.device.metadata);
  }
});
Aplicaciones de Salud #
await scanner.initialize(config: ScannerConfig.healthcare);
scanner.events.listen((event) {
  if (event.device.category == 'medical_device') {
    // Procesar datos médicos
    processMedicalData(event.device);
  }
});
🔧 Personalización Completa #
// Configuración empresarial personalizada
final enterpriseConfig = BeaconConfig(
  scanInterval: 500,
  minRssi: -75,
  maxDistance: 50.0,
  allowedUuids: [
    'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
    'E2C56DB5-DFFB-48D2-B060-D0F5A7100000',
  ],
  enablePersistence: true,
  logLevel: 2,
);
await scanner.initialize(config: enterpriseConfig);
🆘 Manejo de Errores #
try {
  await scanner.startScanning();
} catch (e) {
  if (e.toString().contains('permissions')) {
    // Manejar error de permisos
  } else if (e.toString().contains('bluetooth')) {
    // Manejar error de Bluetooth
  }
}
// Escuchar errores del scanner
scanner.status.listen((status) {
  if (status.isError) {
    handleScannerError(status);
  }
});
✅ Best Practices #
🚫 Errores Comunes y Cómo Evitarlos #
1. Memory Leaks - Olvidar dispose()
❌ MAL:
class MyWidget extends StatefulWidget {
  final scanner = UniversalBeaconScanner();
  
  @override
  Widget build(BuildContext context) {
    scanner.startScanning(); // ¡Nunca se detiene!
    return Container();
  }
}
✅ BIEN:
class MyWidget extends StatefulWidget {
  final scanner = UniversalBeaconScanner();
  
  @override
  void initState() {
    super.initState();
    _initScanner();
  }
  
  @override
  void dispose() {
    scanner.dispose(); // ← CRÍTICO: Libera recursos
    super.dispose();
  }
}
2. Permisos - No verificar antes de escanear
❌ MAL:
await scanner.startScanning(); // ¡Puede fallar sin permisos!
✅ BIEN:
// Opción 1: El SDK maneja permisos automáticamente
await scanner.initialize(config: config);
await scanner.startScanning(); // SDK verifica permisos internamente
// Opción 2: Verificación manual explícita
final permissionManager = PermissionManager();
if (!await permissionManager.hasRequiredPermissions()) {
  final granted = await permissionManager.checkAndRequestAllPermissions();
  if (!granted) {
    showPermissionDialog();
    return;
  }
}
await scanner.startScanning();
3. Configuración Incorrecta para Caso de Uso
❌ MAL:
// Usando batterySaver para control de acceso
config: BeaconConfig.batterySaver() // ¡Detección cada 5s es muy lento!
✅ BIEN:
// Usar el preset correcto según caso de uso
config: BeaconConfig.ultraFast() // Para control de acceso inmediato
4. No Escuchar el Stream Correcto
❌ MAL:
// Usando beaconEvents para actualizar ListView (demasiados updates)
scanner.beaconEvents.listen((event) {
  setState(() {
    _devices = [...]; // ¡Se actualiza 10+ veces por segundo!
  });
});
✅ BIEN:
// Usar detectedDevices para UI (throttled)
scanner.detectedDevices.listen((devices) {
  setState(() {
    _devices = devices; // Se actualiza cada 500ms (configurable)
  });
});
5. Registrar Beacons Innecesariamente
❌ MAL:
// Registrar todos los beacons del mundo "por si acaso"
registeredBeacons: [
  BeaconRegistry(id: 'beacon_1', uuid: '...'),
  BeaconRegistry(id: 'beacon_2', uuid: '...'),
  // ... 500 beacons más
] // ¡Impacto en rendimiento!
✅ BIEN:
// Solo registrar beacons que realmente necesitas identificar
registeredBeacons: [
  BeaconRegistry(id: 'main_door', uuid: '...'),
  BeaconRegistry(id: 'back_door', uuid: '...'),
] // Mantén la lista pequeña y relevante
6. No Manejar Estado de Bluetooth
❌ MAL:
await scanner.startScanning(); // ¿Qué pasa si BT está off?
✅ BIEN:
scanner.status.listen((status) {
  switch (status) {
    case ScannerStatus.error:
      showBluetoothDialog(); // Pedir al usuario activar BT
      break;
    case ScannerStatus.scanning:
      hideLoadingIndicator();
      break;
  }
});
7. Configuración Demasiado Agresiva
❌ MAL:
BeaconConfig(
  scanInterval: 50,           // ¡Muy rápido!
  minUpdateInterval: 10,      // ¡Demasiado frecuente!
  scanMode: ScanMode.lowLatency,
  // Batería se agota en 2 horas
)
✅ BIEN:
// Empieza con preset y ajusta solo si es necesario
BeaconConfig.accessControl() // 500ms es suficiente para mayoría de casos
💡 Recomendaciones Generales #
✅ DO (Hacer)
- Siempre llama 
dispose()cuando el widget se destruye - Usa el preset correcto para tu caso de uso antes de personalizar
 - Escucha 
statuspara mostrar estado de BT/permisos en UI - Prueba en dispositivo real - el emulador no tiene Bluetooth
 - Usa 
detectedDevicespara UI,beaconEventspara acciones - Mantén pequeña la lista de 
registeredBeacons(<50) - Verifica permisos antes de funcionalidad crítica
 - Maneja el caso de Bluetooth apagado
 
❌ DON'T (No Hacer)
- No olvides 
dispose()- causa memory leaks - No uses batterySaver para apps interactivas
 - No registres beacons si no necesitas identificarlos
 - No uses beaconEvents para actualizar listas en UI
 - No asumas permisos están concedidos sin verificar
 - No uses ultraFast si la app corre todo el día
 - No ignores el estado del scanner en UI
 - No escanees continuamente si no es necesario
 
🎯 Checklist de Implementación #
Antes de ir a producción, verifica:
- ❌ ✅ 
dispose()llamado en todos los lugares donde se crea scanner - ❌ ✅ Permisos verificados/solicitados antes de escanear
 - ❌ ✅ Preset correcto elegido según caso de uso
 - ❌ ✅ Streams correctos usados (events vs devices vs status)
 - ❌ ✅ Manejo de Bluetooth apagado implementado
 - ❌ ✅ Manejo de permisos denegados implementado
 - ❌ ✅ UI muestra estado del scanner claramente
 - ❌ ✅ Probado en dispositivo real Android/iOS
 - ❌ ✅ Probado con Bluetooth apagado/encendido
 - ❌ ✅ Probado sin permisos concedidos
 - ❌ ✅ No hay memory leaks (verificado con DevTools)
 - ❌ ✅ Batería consumida es aceptable para tu caso de uso
 
📱 Testing en Dispositivos Reales #
// Ejemplo de test completo
void testBeaconScanner() async {
  final scanner = UniversalBeaconScanner();
  
  print('✓ Test 1: Inicialización');
  await scanner.initialize(config: BeaconConfig.accessControl());
  
  print('✓ Test 2: Verificar permisos');
  assert(await PermissionManager().hasRequiredPermissions());
  
  print('✓ Test 3: Iniciar escaneo');
  await scanner.startScanning();
  
  print('✓ Test 4: Detectar beacons (esperar 10s)');
  await Future.delayed(Duration(seconds: 10));
  final devices = scanner.currentDevices;
  print('  Detectados: ${devices.length} beacons');
  
  print('✓ Test 5: Detener escaneo');
  await scanner.stopScanning();
  
  print('✓ Test 6: Cleanup');
  await scanner.dispose();
  
  print('✅ Todos los tests pasaron');
}
📚 Recursos Adicionales #
📄 Licencia #
MIT License - Ver LICENSE para más detalles.