BluetoothLeScanner — qué es, métodos y escaneo BLE en Android

Autor: IT Sectr Publicado: 2026-07-16 Tiempo de lectura: 10 min

BluetoothLeScanner es una clase de Android para escanear dispositivos Bluetooth Low Energy, disponible desde API 21 (Android 5.0). BluetoothLeScanner reemplazó el método obsoleto startLeScan en BluetoothAdapter, proporcionando una API flexible con configuración de escaneo (ScanSettings), filtrado (ScanFilter) y soporte de modo fondo (PendingIntent). La instancia se obtiene mediante BluetoothAdapter.getBluetoothLeScanner(). Según Android Developers, 2026, BluetoothLeScanner soporta tres modos de energía y permite escanear paquetes de publicidad BLE con filtrado por UUID de servicio, nombre del dispositivo o dirección MAC.

Puntos Clave

  • BluetoothLeScanner — API moderna de Android (API 21+) para escaneo BLE, reemplazo del obsoleto startLeScan
  • ScanSettings — configuración del modo de escaneo: LOW_POWER, BALANCED, LOW_LATENCY y tipo de callback
  • ScanFilter — filtrado de resultados por UUID de servicio, nombre del dispositivo, dirección MAC, datos del fabricante
  • ScanCallback — callbacks de resultados onScanResult, onBatchScanResults y onScanFailed con códigos de error
  • PendingIntent — escaneo en segundo plano mediante BroadcastReceiver, incluso cuando la app está en background

¿Qué es BluetoothLeScanner: esencia y obtención de la instancia?

BluetoothLeScanner es una clase del sistema para gestionar el escaneo BLE en Android. A diferencia de BluetoothAdapter.startLeScan(), que acepta un simple LeScanCallback, BluetoothLeScanner proporciona una API orientada a objetos con configuración, filtros y manejo avanzado de errores. Esta clase se introdujo en API 21 (Android 5.0) junto con el soporte de BLE 4.2 y sigue siendo el método principal de escaneo BLE en todas las versiones modernas de Android.

La obtención de una instancia de BluetoothLeScanner se realiza mediante BluetoothAdapter.getBluetoothLeScanner(). El método devuelve null si el adaptador Bluetooth no está disponible (Bluetooth desactivado o el dispositivo no soporta BLE). Antes de obtenerla, verifique BluetoothAdapter.isEnabled() y la presencia de FEATURE_BLUETOOTH_LE mediante PackageManager. Una vez obtenido el escáner, se puede iniciar el escaneo en cualquier hilo: Android programa las operaciones BLE en un hilo interno de la pila Bluetooth.

kotlin
// Obteniendo BluetoothLeScanner
class BLEScannerManager(context: Context) {

    private val bluetoothManager: BluetoothManager =
        context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
    private val adapter: BluetoothAdapter? = bluetoothManager.adapter
    private var scanner: BluetoothLeScanner? = null

    fun initScanner(): Boolean {
        // Verificar disponibilidad de BLE
        if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            return false
        }

        // Verificar Bluetooth activado
        if (adapter?.isEnabled != true) {
            return false
        }

        // Obtener escáner
        scanner = adapter?.bluetoothLeScanner
        return scanner != null
    }

    // Verificar disponibilidad del escáner
    val isAvailable: Boolean
        get() = scanner != null

    // Iniciar escaneo básico sin filtros
    fun startBasicScan() {
        scanner?.startScan(object : ScanCallback() {
            override fun onScanResult(callbackType: Int, result: ScanResult) {
                handleResult(result)
            }
        })
    }

    private fun handleResult(result: ScanResult) {
        val device = result.device
        print("Device: ${device.name ?: "Unnamed"}, RSSI: ${result.rssi}, address: ${device.address}")
    }
}

La clase BLEScannerManager demuestra la obtención e inicialización segura de BluetoothLeScanner. initScanner verifica la disponibilidad de BLE mediante hasSystemFeature, Bluetooth activado y la obtención exitosa del escáner. startBasicScan inicia el escaneo sin configuración ni filtros — descubre todos los dispositivos BLE en el alcance. handleResult analiza ScanResult: BluetoothDevice (nombre, dirección), RSSI (intensidad de la señal), scanRecord (datos de publicidad).

ScanSettings: modos de escaneo y tipo de callback

ScanSettings es una clase para la configuración del escaneo BLE. El parámetro principal es el modo de escaneo (scanMode), que determina el equilibrio entre el consumo de energía y la latencia de detección. ScanSettings.Builder permite configurar: scanMode, callbackType (CALLBACK_TYPE_ALL_MATCHES, CALLBACK_TYPE_FIRST_MATCH, CALLBACK_TYPE_MATCH_LOST), matchMode (MATCH_MODE_AGGRESSIVE, MATCH_MODE_STICKY), reportDelay (retardo de entrega por lotes) y phy (PHY_LE_1M, PHY_LE_2M, PHY_LE_CODED).

Tres modos de escaneo: SCAN_MODE_LOW_POWER (0) — escaneo en segundo plano con bajo consumo, retardo de detección de varios segundos. SCAN_MODE_BALANCED (1) — modo equilibrado para la mayoría de escenarios. SCAN_MODE_LOW_LATENCY (2) — latencia de detección mínima (unos 100 ms), consumo máximo. Para la búsqueda activa de dispositivos use LOW_LATENCY, para monitoreo en segundo plano use LOW_POWER.

reportDelay — retardo en milisegundos antes de la entrega por lotes de resultados. Si reportDelay = 0, los resultados se envían inmediatamente al detectarse. Si > 0, Android acumula resultados y envía un lote mediante onBatchScanResults. La entrega por lotes reduce las invocaciones de callbacks y el consumo de energía, adecuada para escaneo en segundo plano de baja prioridad.

kotlin
// Configuración de ScanSettings para diferentes escenarios
class ScanSettingsProvider {

    // 1. Escaneo rápido (búsqueda activa)
    fun lowLatencyScan(): ScanSettings {
        return ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
            .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
            .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
            .setReportDelay(0)
            .setPhy(ScanSettings.PHY_LE_ALL_SUPPORTED)
            .build()
    }

    // 2. Escaneo eficiente en energía (monitoreo en fondo)
    fun lowPowerScan(): ScanSettings {
        return ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
            .setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
            .setMatchMode(ScanSettings.MATCH_MODE_STICKY)
            .setReportDelay(2000)  // lote cada 2 segundos
            .build()
    }

    // 3. Escaneo BLE Long Range (Coded PHY)
    fun longRangeScan(): ScanSettings {
        return ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
            .setPhy(ScanSettings.PHY_LE_CODED)
            .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
            .build()
    }

    // 4. Escanear solo en 2M PHY (BLE 5.0+)
    fun highSpeedScan(): ScanSettings {
        return ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
            .setPhy(ScanSettings.PHY_LE_2M)
            .build()
    }
}

La clase ScanSettingsProvider contiene configuraciones típicas. lowLatencyScan — para escaneo de UI (búsqueda “aquí y ahora”). lowPowerScan — para monitoreo en segundo plano con lote cada 2 segundos y callbackType FIRST_MATCH (se activa solo en la primera detección). longRangeScan usa PHY_LE_CODED (BLE Long Range, hasta 1 km). highSpeedScan — PHY_LE_2M (2 Mbit/s, solo dispositivos BLE 5.0+).

ScanFilter: filtrado de dispositivos BLE por UUID y nombre

ScanFilter es una clase para filtrar resultados de escaneo BLE. Sin filtro, BluetoothLeScanner devuelve todos los dispositivos BLE en el alcance — en un entorno BLE denso pueden ser cientos de paquetes por minuto. ScanFilter reduce los resultados a los dispositivos deseados, disminuyendo el consumo de energía y la carga de la aplicación. Los filtros se aplican a nivel de la pila Bluetooth: los paquetes no coincidentes se descartan antes de llegar a la app.

Tipos de filtros: setServiceUuid — UUID del servicio (formato completo de 128 bits requerido). setDeviceName — subcadena del nombre del dispositivo (distingue mayúsculas, coincidencia exacta de subcadena). setDeviceAddress — dirección MAC exacta. setManufacturerData — datos del fabricante (ID de la compañía + máscara). Se pueden establecer varios filtros para un solo escaneo: el dispositivo debe coincidir con todos (lógica AND). Para lógica OR, inicie varios escaneos.

kotlin
// Creación de ScanFilter para diferentes escenarios
class ScanFilterFactory {

    // 1. Filtrar por UUID de servicio (Heart Rate Monitor)
    fun byHeartRateService(): ScanFilter {
        return ScanFilter.Builder()
            .setServiceUuid(
                ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB")
            )
            .build()
    }

    // 2. Filtrar por nombre del dispositivo ( "iBeacon*")
    fun byDeviceName(): ScanFilter {
        return ScanFilter.Builder()
            .setDeviceName("Sensor")
            .build()
    }

    // 3.   MAC- ( dispositivos)
    fun byMacAddress(mac: String): ScanFilter {
        return ScanFilter.Builder()
            .setDeviceAddress(mac)
            .build()
    }

    // 4. Filtro combinado (UUID + )
    fun combinedFilter(): List<ScanFilter> {
        return listOf(
            ScanFilter.Builder()
                .setServiceUuid(
                    ParcelUuid.fromString("0000A001-0000-1000-8000-00805F9B34FB")
                )
                .setDeviceName("MyDevice")
                .build()
        )
    }

    // 5.   datos del fabricante
    fun byManufacturer(companyId: Int, data: ByteArray, mask: ByteArray): ScanFilter {
        return ScanFilter.Builder()
            .setManufacturerData(companyId, data, mask)
            .build()
    }
}

La clase ScanFilterFactory muestra todos los tipos de filtros. byHeartRateService filtra dispositivos con el servicio de frecuencia cardíaca 0x180D. byDeviceName encuentra dispositivos que contengan “Sensor” en el nombre (Apple recomienda nombres únicos para filtrado). byMacAddress — búsqueda exacta de un dispositivo específico. combinedFilter — filtro AND por UUID y nombre. byManufacturer — filtro por datos del fabricante (por ejemplo, para iBeacon se usa el ID de compañía de Apple 0x004C).

ScanCallback: manejo de resultados y errores de escaneo

ScanCallback es una clase abstracta para recibir resultados de escaneo BLE. Contiene tres métodos: onScanResult — resultado individual (tipo de callback, ScanResult), onBatchScanResults — resultados por lotes para reportDelay > 0, onScanFailed — código de error. Todos los métodos se llaman en el hilo principal de Android. Para procesamiento largo en onScanResult, use corrutinas o HandlerThread.

ScanResult contiene: BluetoothDevice device, int rssi (nivel de señal en dBm), ScanRecord scanRecord (datos de publicidad), long timestampNanos (tiempo de detección desde el inicio del sistema). ScanRecord proporciona: getServiceData() — UUID + datos personalizados, getManufacturerSpecificData() — datos del fabricante, getAdvertiseFlags() — banderas BLE. El tipo de callback indica: CALLBACK_TYPE_ALL_MATCHES — coincidencia con el filtro, CALLBACK_TYPE_FIRST_MATCH — primera detección, CALLBACK_TYPE_MATCH_LOST — dispositivo perdido.

Códigos de error de onScanFailed: SCAN_FAILED_ALREADY_STARTED (1) — escaneo ya iniciado, SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2) — falló el registro de la app en la pila Bluetooth, SCAN_FAILED_INTERNAL_ERROR (3) — error interno de la pila, SCAN_FAILED_FEATURE_UNSUPPORTED (4) — escaneo BLE no soportado en el dispositivo.

kotlin
// Resultados completos y manejo de errores
class ScanResultHandler {

    private val results = mutableListOf<ScanResult>()

    val scanCallback = object : ScanCallback() {

        // 1. Resultado individual
        override fun onScanResult(callbackType: Int, result: ScanResult) {
            // callbackType: 1 = ALL_MATCHES, 2 = FIRST_MATCH, 4 = MATCH_LOST
            if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST) {
                onDeviceLost(result)
                return
            }

            // Agregar a la lista (desduplicar por dirección)
            val existingIndex = results.indexOfFirst {
                it.device.address == result.device.address
            }
            if (existingIndex >= 0) {
                results[existingIndex] = result  // actualizar RSSI
            } else {
                results.add(result)
            }

            // Extraer datos del paquete de publicidad
            val record = result.scanRecord
            val serviceData = record?.serviceData
            val manufacturerData = record?.manufacturerSpecificData

            print("Found: ${result.device.name ?: "N/A"}, RSSI: ${result.rssi}")
        }

        // 2. Resultados por lotes (reportDelay > 0)
        override fun onBatchScanResults(results: MutableList<ScanResult>?) {
            results?.let { batch ->
                print("Batch: ${batch.size} devices")
            }
        }

        // 3. Error de escaneo
        override fun onScanFailed(errorCode: Int) {
            val error = when (errorCode) {
                ScanCallback.SCAN_FAILED_ALREADY_STARTED -> "Already scanning"
                ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED -> "Registration failed"
                ScanCallback.SCAN_FAILED_INTERNAL_ERROR -> "Internal error"
                ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED -> "BLE not supported"
                else -> "Unknown error: $errorCode"
            }
            print("Error: $error")
        }
    }

    private fun onDeviceLost(result: ScanResult) {
        results.removeAll { it.device.address == result.device.address }
        print("Device lost: ${result.device.address}")
    }
}

La clase ScanResultHandler maneja todos los tipos de callbacks de BluetoothLeScanner. onScanResult actualiza la lista de dispositivos con desduplicación por dirección MAC — RSSI se actualiza para dispositivos ya encontrados. CALLBACK_TYPE_MATCH_LOST señala la pérdida del dispositivo (eliminación de la lista). onBatchScanResults procesa resultados por lotes para reportDelay > 0. onScanFailed mapea códigos de error a mensajes legibles — crítico para la depuración de escaneo BLE.

PendingIntent: escaneo BLE en segundo plano mediante BroadcastReceiver

Escaneo con PendingIntent es un mecanismo de BluetoothLeScanner para escaneo BLE que funciona incluso cuando la app está en segundo plano (con restricciones de Android 8+). En lugar de ScanCallback, se usa un PendingIntent que envía un Broadcast al BroadcastReceiver del sistema cuando se descubre un dispositivo BLE. Esto permite que la app reciba notificaciones de dispositivos BLE sin permanecer en memoria (el sistema crea el proceso al recibir el broadcast).

Limitaciones del escaneo en segundo plano: En Android 8+ (API 26), los servicios en segundo plano están restringidos — el escaneo con PendingIntent evita esta limitación mediante BroadcastReceiver, que el sistema puede iniciar al recibir un evento BLE. En Android 10+ (API 29), el escaneo BLE en segundo plano tiene restricciones adicionales por políticas de ahorro de energía de los fabricantes (Xiaomi, Huawei, Samsung bloquean operaciones BLE en segundo plano). Para escenarios BLE críticos, se requiere una notificación de foreground service.

kotlin
// Escaneo BLE en fondo mediante PendingIntent
class BackgroundBLEScanner(private val context: Context) {

    private val scanner: BluetoothLeScanner? by lazy {
        val adapter = BluetoothAdapter.getDefaultAdapter()
        adapter?.bluetoothLeScanner
    }

    fun startBackgroundScan() {
        // Crear PendingIntent para BroadcastReceiver
        val intent = Intent(context, BLEBroadcastReceiver::class.java)
        val pendingIntent = PendingIntent.getBroadcast(
            context,
            0,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        // Configuración de escaneo en fondo
        val settings = ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
            .setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
            .setMatchMode(ScanSettings.MATCH_MODE_STICKY)
            .build()

        // Iniciar escaneo en fondo
        scanner?.startScan(
            null,  // filtros
            settings,
            pendingIntent
        )
    }

    fun stopBackgroundScan() {
        val intent = Intent(context, BLEBroadcastReceiver::class.java)
        val pendingIntent = PendingIntent.getBroadcast(
            context, 0, intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
        scanner?.stopScan(pendingIntent)
    }
}

// BroadcastReceiver   BLE-
class BLEBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        // Obtener resultados del escaneo
        val results = BluetoothLeScanner.getPendingIntentScanResults(intent)
        results?.let { scanResults ->
            for (result in scanResults) {
                // Enviar notificación al usuario
                showNotification(context, result.device.name ?: " ")
            }
        }
    }

    private fun showNotification(context: Context, name: String) {
        val notification = Notification.Builder(context, "ble_channel")
            .setSmallIcon(android.R.drawable.ic_dialog_info)
            .setContentTitle("BLE devices")
            .setContentText("Found: $name")
            .setAutoCancel(true)
            .build()
        val manager = context.getSystemService(Context.NOTIFICATION_SERVICE)
                as NotificationManager
        manager.notify(System.currentTimeMillis().toInt(), notification)
    }
}

La clase BackgroundBLEScanner inicia el escaneo BLE en segundo plano mediante PendingIntent. startBackgroundScan crea un PendingIntent que, al descubrir un dispositivo BLE, envía un Broadcast a BLEBroadcastReceiver. El BroadcastReceiver extrae ScanResult mediante getPendingIntentScanResults() y puede mostrar una notificación o enviar datos al servidor. Este enfoque funciona incluso si la app fue terminada por el sistema — Android reinicia el BroadcastReceiver al recibir el broadcast.

Ejemplo de escáner BLE en Kotlin con BluetoothLeScanner

Ejemplo completo de un escáner BLE en Kotlin que usa BluetoothLeScanner con ScanSettings, ScanFilter y ScanCallback para encontrar dispositivos Heart Rate Monitor. El escáner muestra una lista de dispositivos encontrados con RSSI y UUIDs de servicio, con la capacidad de conectar mediante BluetoothGatt.

kotlin
// Escáner BLE completo con corrutinas en Kotlin
class DeviceScanner(private val context: Context) {

    private val adapter: BluetoothAdapter? by lazy {
        val manager = context.getSystemService(Context.BLUETOOTH_SERVICE)
                as BluetoothManager
        manager.adapter
    }

    private val scanner: BluetoothLeScanner? by lazy {
        adapter?.bluetoothLeScanner
    }

    fun startScan(duration: Long = 10000): Flow<ScanResult> = callbackFlow {
        // Verificar estado de Bluetooth
        if (adapter?.isEnabled != true) {
            close(IllegalStateException("Bluetooth disabled"))
            return@callbackFlow
        }

        // Configuración de escaneo
        val settings = ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
            .build()

        val filters = listOf(
            ScanFilter.Builder()
                .setServiceUuid(ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB"))
                .build()
        )

        val callback = object : ScanCallback() {
            override fun onScanResult(callbackType: Int, result: ScanResult) {
                trySend(result)
            }

            override fun onScanFailed(errorCode: Int) {
                close(BLEException("Scan failed: $errorCode"))
            }
        }

        // Iniciar escaneo
        scanner?.startScan(filters, settings, callback)

        // Auto-parada después de duración
        delay(duration)
        scanner?.stopScan(callback)
        close()
    }.flowOn(Dispatchers.IO)

    fun stop() {
        scanner?.stopScan(object : ScanCallback() {})
    }
}

class BLEException(message: String) : Exception(message)

La clase DeviceScanner usa Kotlin Flow (callbackFlow) para escaneo BLE reactivo. El escaneo se inicia con configuración LOW_LATENCY y un filtro de UUID de Heart Rate Service. Los resultados se emiten mediante onScanResult a un Flow. Auto-parada después de una duración determinada (10 segundos por defecto). FlowOn(Dispatchers.IO) transfiere las operaciones BLE a un hilo en segundo plano. Este enfoque permite el escaneo BLE en arquitectura MVVM mediante viewModelScope.launch y collect.

Preguntas Frecuentes

¿Qué es BluetoothLeScanner en Android?

BluetoothLeScanner es una clase de Android (API 21+) para escaneo BLE. Se obtiene mediante BluetoothAdapter.getBluetoothLeScanner(). Soporta tres modos de escaneo (LOW_POWER, BALANCED, LOW_LATENCY), filtrado por UUID, nombre y dirección MAC, resultados por lotes y PendingIntent para escaneo en segundo plano. Reemplaza el método obsoleto BluetoothAdapter.startLeScan().

¿En qué se diferencia LOW_POWER de LOW_LATENCY?

SCAN_MODE_LOW_POWER — modo fondo con retardo de detección de 5–10 segundos, consumo mínimo. SCAN_MODE_LOW_LATENCY — modo activo con retardo de unos 100 ms, consumo máximo. SCAN_MODE_BALANCED — compromiso (~2 segundos de retardo). Use LOW_LATENCY para escaneo de UI, LOW_POWER con PendingIntent para monitoreo en segundo plano.

¿Por qué BluetoothLeScanner no encuentra dispositivos?

Razones: Bluetooth desactivado (verifique adapter.isEnabled), permisos faltantes (BLUETOOTH_SCAN en API 31+, ACCESS_FINE_LOCATION en API 23–30), scanner = null (adaptador no disponible), dispositivo fuera de alcance, o filtro incorrecto. También verifique onScanFailed — el código de error indica la causa: SCAN_FAILED_ALREADY_STARTED (1) o SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2).

¿Cómo escanear BLE en segundo plano en Android?

Use la versión PendingIntent de startScan() — pase un PendingIntent en lugar de ScanCallback. Cuando se descubre un dispositivo BLE, Android envía un Broadcast al BroadcastReceiver, que puede ser iniciado por el sistema incluso si la app está en segundo plano. Para Android 8+, agregue el BroadcastReceiver al manifiesto. En Android 10+, considere las restricciones de ahorro de energía de los fabricantes.

¿Cuántos dispositivos BLE se pueden descubrir en un solo escaneo?

BluetoothLeScanner no tiene límite en la cantidad de dispositivos descubiertos — la limitación depende de la saturación BLE del entorno. Una oficina puede tener 20–50 dispositivos BLE activos, un centro comercial cientos. Use ScanFilter (por UUID, nombre) para filtrar. Sin filtrado, procese los resultados de forma asíncrona — onScanResult puede llamarse docenas de veces por segundo.

Resumen

  • BluetoothLeScanner — clase moderna de Android (API 21+) para escaneo BLE con configuración y filtrado
  • ScanSettings — tres modos: LOW_POWER (fondo), BALANCED, LOW_LATENCY (activo)
  • ScanFilter — filtrado por UUID de servicio, nombre del dispositivo, dirección MAC, datos del fabricante con lógica AND
  • ScanCallback — onScanResult (individual), onBatchScanResults (por lotes), onScanFailed (códigos de error)
  • PendingIntent — escaneo en segundo plano mediante BroadcastReceiver, funciona cuando la app está terminada
  • ScanRecord — datos de publicidad BLE: serviceData, manufacturerSpecificData, advertiseFlags, TX power level
  • Kotlin Flow — callbackFlow permite usar BluetoothLeScanner en arquitectura reactiva con auto-parada

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también