Bot de Telegram para preparar el examen teórico del permiso de conducir B en España.
Práctica infinita, exámenes tipo DGT de 30 preguntas con suspenso a los 4 fallos, banco de preguntas navegable y estadísticas personales. Todo dentro de un chat de Telegram, sin app web ni Anki.
Este repositorio es autocontenido: incluye el código del bot, el dataset de preguntas (data_B.json) y las 2.776 imágenes del banco (Imagenes/). No necesitas ningún otro repo ni servicio externo.
- Qué hace
- Requisitos
- Tutorial 1 — Crear tu propio bot de Telegram
- Tutorial 2 — Montarlo en local
- Variables de entorno
- Cómo funcionan las imágenes
- Estructura del proyecto
- Tests
- Problemas frecuentes
- Licencia y atribución
| Comando | Qué hace |
|---|---|
/start, /menu |
Menú principal con botones y estado del banco de preguntas |
/practica |
Práctica infinita: pregunta aleatoria, respondes A/B/C y ves si has acertado con la explicación |
/examen |
Examen de 30 preguntas al estilo DGT. Se suspende con más de 3 fallos |
/estadisticas |
Aciertos, fallos y precisión, separados entre práctica y examen. Se pueden reiniciar |
/banco |
Navegar y consultar el banco completo de preguntas con su explicación |
/ayuda |
Ayuda y lista de comandos |
Detalles de implementación que conviene conocer:
- Sin servidor de estado: todo vive en un SQLite local (
botcarnet.db). Las estadísticas son poruser_idde Telegram. - Las imágenes se cachean solas: la primera vez que una pregunta con foto se envía, Telegram devuelve un
file_idque se guarda en la base de datos. A partir de ahí no se vuelve a subir el fichero, se reenvía elfile_id. Es mucho más rápido y no consume ancho de banda. - El banco es una copia propia: el dataset se importa una vez a SQLite. Puedes editar preguntas sin depender del repositorio original.
- Python 3.11 o superior
- Una cuenta de Telegram
- Git
No hace falta base de datos, Docker ni cuenta en ningún proveedor cloud para usarlo en local.
Cada persona necesita su propio bot con su propio token. El token es una credencial: quien lo tenga controla el bot, así que no lo compartas ni lo subas a GitHub.
@BotFather es el bot oficial de Telegram para crear bots.
- Abre Telegram y busca @BotFather (el que tiene la marca de verificación azul).
- Pulsa Iniciar / envía
/start.
-
Envía
/newbot. -
BotFather te pide un nombre visible. Es el que verá la gente en el chat. Ejemplo:
Test Carnet B. -
Después te pide un username. Tiene que ser único en todo Telegram y terminar en
bot. Ejemplo:mi_test_carnetb_bot.- Si te dice
Sorry, this username is already taken, prueba otro.
- Si te dice
-
BotFather responde con un mensaje que contiene el token, con esta pinta:
Use this token to access the HTTP API: 123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsawCopia ese token, lo necesitas en el Tutorial 2.
Con BotFather puedes personalizarlo:
/setuserpic— foto de perfil del bot./setdescription— texto que se ve antes de pulsar «Iniciar»./setabouttext— descripción corta del perfil./setprivacy— no hace falta tocarlo: este bot funciona en chats privados.
No necesitas configurar los comandos a mano: el propio bot registra su menú de comandos, su descripción y su descripción corta al arrancar, así que aparecerán solos la primera vez que lo ejecutes.
Vuelve a @BotFather → /mybots → elige tu bot → API Token. Si crees que se ha filtrado, ahí mismo puedes usar Revoke current token para invalidarlo y generar uno nuevo.
git clone https://github.com/jaimeberdejo/BotCarnetConducir.git
cd BotCarnetConducirEl repo pesa unos 150 MB porque incluye las imágenes de las preguntas. Si solo quieres el código y prefieres que las imágenes se descarguen bajo demanda desde GitHub, clona sin historial y borra la carpeta:
git clone --depth 1 https://github.com/jaimeberdejo/BotCarnetConducir.git cd BotCarnetConducir && rm -rf ImagenesEl bot detecta que no hay imágenes locales y las baja de la URL pública. Ver Cómo funcionan las imágenes.
macOS / Linux:
python3 -m venv .venv
source .venv/bin/activate
pip install -e .Windows (PowerShell):
py -m venv .venv
.venv\Scripts\Activate.ps1
pip install -e .Esto instala la única dependencia real: python-telegram-bot (v21).
Copia la plantilla y pega el token que te dio BotFather:
cp .env.example .envEdita .env:
BOT_TOKEN=123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw.env está en .gitignore, así que no se sube al repositorio. Alternativamente puedes exportar la variable en tu shell (export BOT_TOKEN=...) en lugar de usar el fichero.
python -m botcarnetEn el primer arranque el bot:
- Crea
botcarnet.db(SQLite). - Importa las 2.947 preguntas de
data_B.json. - Registra el menú de comandos y la descripción en Telegram.
- Se queda escuchando (
polling).
Verás algo así en la consola:
2026-01-01 12:00:00 INFO botcarnet.app :: Preguntas importadas en este arranque: 2947
2026-01-01 12:00:00 INFO botcarnet.app :: Total preguntas importadas: 2947
Los arranques siguientes son instantáneos: la base ya está poblada y la importación es idempotente (hace upsert, no duplica).
Abre Telegram, busca tu bot por el username que elegiste (@mi_test_carnetb_bot), pulsa Iniciar y envía /practica.
Para pararlo, Ctrl+C en la terminal. El bot solo responde mientras el proceso esté corriendo: si cierras la terminal o apagas el ordenador, deja de responder. Para tenerlo activo de forma permanente hay que ejecutarlo en una máquina que no se apague; el Dockerfile incluido sirve para desplegarlo en cualquier plataforma que corra contenedores.
Todas son opcionales salvo BOT_TOKEN.
| Variable | Por defecto | Para qué sirve |
|---|---|---|
BOT_TOKEN |
(obligatoria) | Token del bot que te da @BotFather. Sin ella el arranque falla con BOT_TOKEN no configurado. |
BOT_DB_PATH |
botcarnet.db |
Ruta del SQLite. En despliegues, apúntala a un volumen persistente |
BOT_IMAGES_DIR |
Imagenes |
Carpeta de imágenes locales. Si no existe, se usa la URL remota |
BOT_DATASET_JSON |
data_B.json |
Copia local del dataset. Si no existe, se descarga de BOT_DATASET_URL |
BOT_DATASET_URL |
Dataset de donmerendolo/anki-carnet-conducir |
De dónde bajar el JSON si no hay copia local |
BOT_IMAGE_BASE_URL |
.../BotCarnetConducir/master/Imagenes/ |
Base para servir imágenes por HTTP cuando no hay copia local |
Dos constantes de examen viven en el código (src/botcarnet/settings.py) por si quieres ajustarlas: exam_question_count (30) y exam_fail_threshold (3).
El bot resuelve la foto de cada pregunta en tres niveles, en este orden:
telegram_file_idguardado en la base de datos — si la pregunta ya se envió antes, se reenvía el identificador de Telegram. Instantáneo, sin tráfico.- Fichero local en
BOT_IMAGES_DIR— si tienes la carpetaImagenes/clonada. Se sube el fichero y se guarda elfile_idresultante para la próxima vez. - Descarga desde
BOT_IMAGE_BASE_URL— se baja deraw.githubusercontent.comy se envía, guardando también elfile_id.
Si los tres fallan, la pregunta se manda como texto: nunca se rompe el flujo por una imagen.
Esto significa que puedes desplegar sin las imágenes para que el contenedor pese poco (es justo lo que hace el Dockerfile, que no las copia): se sirven desde GitHub la primera vez y a partir de ahí van por file_id.
Si haces fork del repo, acuérdate de apuntar BOT_IMAGE_BASE_URL a tu fork, y de que el repo sea público — raw.githubusercontent.com no sirve ficheros de repositorios privados.
BotCarnetConducir/
├── src/botcarnet/
│ ├── __main__.py # Punto de entrada: python -m botcarnet
│ ├── app.py # Handlers de Telegram, menús y flujos de práctica/examen
│ ├── db.py # Esquema SQLite y todas las consultas
│ ├── importer.py # Carga data_B.json al banco (upsert idempotente)
│ ├── models.py # Question / QuestionDraft
│ ├── render.py # Formato de mensajes y teclados inline
│ └── settings.py # Configuración por entorno + carga de .env
├── tests/ # Tests del importador
├── Imagenes/ # 2.776 imágenes del banco de preguntas
├── data_B.json # Dataset de preguntas del permiso B
├── Dockerfile # Imagen para despliegue (sin Imagenes/)
└── pyproject.toml
pip install pytest
pytestCubren la normalización del dataset y la resolución de nombres de imagen del importador (el dataset original mezcla extensiones .jpg y .JPG).
RuntimeError: BOT_TOKEN no configurado.
No hay .env en el directorio desde el que lanzas el comando, o la variable está vacía. Ojo: .env se busca en el directorio de trabajo actual, no en el del paquete. Ejecuta python -m botcarnet desde la raíz del repo.
telegram.error.InvalidToken
El token está mal copiado (suele faltar un trozo, o sobra un espacio). Recupéralo con /mybots en @BotFather.
Conflict: terminated by other getUpdates request
Tienes dos instancias del mismo bot corriendo a la vez — por ejemplo, una en local y otra en un servidor. Telegram solo permite un consumidor de updates por token. Para una de las dos.
El bot no responde y no hay error en consola Comprueba que estás escribiendo al bot correcto (el username exacto que creaste) y que pulsaste Iniciar.
Las preguntas llegan sin foto
Se está usando la ruta remota y la descarga falla. Verifica que BOT_IMAGE_BASE_URL apunta a un repositorio público y que la URL termina en /. Prueba a abrir en el navegador https://raw.githubusercontent.com/jaimeberdejo/BotCarnetConducir/master/Imagenes/B_32.jpg.
Quiero empezar de cero
Borra botcarnet.db y vuelve a arrancar. Se reimporta el banco y se pierden todas las estadísticas.
Este proyecto se distribuye bajo GPL-3.0 (ver LICENSE).
El banco de preguntas (data_B.json) y las imágenes (Imagenes/) proceden de donmerendolo/anki-carnet-conducir, publicado bajo GPL-3.0. Se redistribuyen aquí bajo la misma licencia, con atribución al proyecto original.
Es material de estudio no oficial: no está avalado por la DGT ni sustituye a los tests oficiales.