Blog · Desarrollo móvil · Marzo 2026

Cómo construí DGTest Autoescuela: simulacros del examen de conducir para Android

KotlinAndroidRoomJetpackAdMob
DGTest Autoescuela — gráfico de funciones de la aplicación

DGTest Autoescuela es una app Android para preparar el examen teórico de conducir en España: simulacros con las preguntas oficiales de la DGT, analítica de progreso por temas y modo offline completo. Está disponible gratis en Google Play y puedes ver su ficha completa en la sección de apps.

En este artículo explico cómo tomé las decisiones técnicas clave: desde la elección de la fuente de datos hasta la arquitectura offline-first con Room, pasando por el motor de analítica de progreso y la integración de AdMob sin perjudicar la experiencia de estudio.

La idea: un problema real con datos abiertos

El examen teórico de conducir en España tiene una tasa de suspensos alta. La mayoría de los aspirantes estudia con las preguntas oficiales que la DGT pone a disposición pública, pero las apps existentes o estaban desactualizadas, o requerían conexión constante, o saturaban al usuario con anuncios en mitad de un simulacro.

La oportunidad era clara: una app con el banco de preguntas oficial, modo offline real (no solo caché), y analítica honesta para que el alumno supiera exactamente en qué temas flojeaba. Todo en una sola descarga, gratis.

Arquitectura: MVVM offline-first con Room

El requisito de offline-first determina la arquitectura desde el principio. No puedes depender de una API: las preguntas tienen que estar en el dispositivo antes de que el usuario abra la app por primera vez. Elegí Room como base de datos local por tres razones:

La arquitectura sigue el patrón MVVM con tres capas bien separadas: el QuizViewModel gestiona el estado de la sesión; el QuestionRepository abstrae el acceso a Room; y la capa de UI con Fragments y un StateFlow que refleja cualquier cambio de estado automáticamente.

data class QuizState( val question: Question, val selectedOption: Int? = null, val isAnswered: Boolean = false, val progress: QuizProgress ) class QuizViewModel( private val repo: QuestionRepository ) : ViewModel() { private val _state = MutableStateFlow<QuizState?>(null) val state: StateFlow<QuizState?> = _state.asStateFlow() fun loadSession(topicId: Int) = viewModelScope.launch { val questions = repo.getShuffledByTopic(topicId) _state.value = QuizState(questions.first(), progress = QuizProgress(questions.size)) } }

El banco de preguntas: pre-population con Room

Las preguntas oficiales de la DGT se distribuyen en un dataset estructurado. Procesé ese dataset para generar una base de datos SQLite pre-poblada que se empaqueta directamente en los assets de la app. Room ofrece soporte nativo para esto mediante createFromAsset() al construir la instancia de la base de datos:

Room.databaseBuilder( context, DGTestDatabase::class.java, "dgtest.db" ) .createFromAsset("database/dgtest_preloaded.db") .addMigrations(MIGRATION_1_2) .build()

Decisión de diseño: Pre-poblar la base de datos en build time tiene un coste: el APK pesa más. Pero para una app de estudio offline, ese intercambio vale la pena: el usuario puede usar la app en el metro, sin WiFi, desde el primer arranque, sin ninguna pantalla de "descargando contenido".

Analítica de progreso: saber dónde fallas

La analítica de progreso es la funcionalidad que más valoran los usuarios según las reseñas. Registré cada respuesta con su resultado, el tema y el tiempo de respuesta. Esto permite calcular métricas útiles por tema:

Todo el historial se guarda en Room con una tabla AnswerRecord que relaciona cada respuesta con el ID de la pregunta, el tema, si fue correcta y el timestamp. Las consultas de analítica son simples agregaciones SQL que Room ejecuta en segundo plano con suspend fun en el DAO.

@Dao interface ProgressDao { @Query("SELECT topicId, AVG(CASE WHEN correct THEN 1.0 ELSE 0 END) AS accuracy FROM answer_records GROUP BY topicId") suspend fun getAccuracyByTopic(): List<TopicAccuracy> @Query("SELECT * FROM answer_records WHERE correct = 0 AND answeredAt > :since ORDER BY answeredAt DESC") suspend fun getRecentMistakes(since: Long): List<AnswerRecord> }

Modo simulacro: reproducir el examen real

El examen de conducir de la DGT tiene formato fijo: 30 preguntas, 30 minutos, máximo 3 fallos. El modo simulacro de DGTest replica exactamente esas condiciones para que el alumno llegue sin sorpresas el día del examen:

Monetización con AdMob: sin interrumpir el estudio

Monetizar una app de estudio es más delicado que monetizar un juego. Un anuncio en mitad de una pregunta destruye la concentración y genera reseñas negativas. Decidí restringir los anuncios a dos momentos neutros:

La lógica de frecuencia es idéntica a la que usé en ConnectAll: un contador en SharedPreferences que solo activa el interstitial cuando se cumple el umbral mínimo de sesiones.

Lanzamiento en Google Play

El proceso de publicación fue similar al de ConnectAll, con un detalle adicional: las apps educativas con contenido de exámenes oficiales necesitan dejar claro en la descripción que el contenido es de preparación y no el examen oficial de la DGT. El equipo de revisión de Google Play lo verificó antes de aprobar la app.

Lecciones aprendidas

Prueba DGTest Autoescuela en Google Play

Simulacros oficiales DGT, analítica de progreso y modo offline. Gratis en Android.

↓ Descargar gratis en Google Play Ver ficha de la app →