from flask import Flask, render_template, jsonify, request, session from flask_cors import CORS import os import uuid import json import requests from datetime import datetime from datasets import Dataset, load_dataset from huggingface_hub import HfApi import logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) app = Flask(__name__) app.secret_key = os.environ.get('SECRET_KEY', 'educateenIA_secret_key_2024') CORS(app) DATA_DIR = os.environ.get('DATA_DIR', '/data') if not os.path.exists(DATA_DIR): DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') os.makedirs(DATA_DIR, exist_ok=True) REPO_ID = os.environ.get('REPO_ID', 'Edoruin/EducateEnIA-data') HF_TOKEN = os.environ.get('HF_TOKEN') or os.environ.get('HUGGING_FACE_HUB_TOKEN') logger.info(f"HF Config inicializada - REPO_ID: {REPO_ID}, HF_TOKEN: {'configurado' if HF_TOKEN else 'NO configurado'}") def get_hf_api(): token = HF_TOKEN or os.environ.get('HUGGING_FACE_HUB_TOKEN') if token and token.startswith('hf_'): try: return HfApi(token=token) except Exception as e: logger.warning(f"Error al crear API de HF: {e}") return None def load_from_hub(filename, fallback=None): token = HF_TOKEN or os.environ.get('HUGGING_FACE_HUB_TOKEN') if not REPO_ID or not token: logger.info(f"Cargando {filename} desde disco local (HF no configurado)") return load_from_disk(filename, fallback) try: url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/{filename}.json" response = requests.get(url, headers={"Authorization": f"Bearer {token}"}) if response.status_code == 200: data = response.json() logger.info(f"Cargado {filename} desde HF Hub") return Dataset.from_list(data) except Exception as e: logger.warning(f"Error al cargar {filename} desde HF: {e}") return load_from_disk(filename, fallback) def save_to_hub(ds, filename): if not REPO_ID: logger.warning(f"REPO_ID no configurado, guardando solo en disco local") save_to_disk(ds, filename) return token_to_use = HF_TOKEN or os.environ.get('HUGGING_FACE_HUB_TOKEN') if not token_to_use: logger.warning(f"HF_TOKEN no configurado, guardando solo en disco local") save_to_disk(ds, filename) return try: api = HfApi(token=token_to_use) data = ds.to_list() json_str = json.dumps(data, indent=2) api.upload_file( path_or_fileobj=json_str.encode(), path_in_repo=f"{filename}.json", repo_id=REPO_ID, repo_type="dataset", commit_message=f"Update {filename}" ) logger.info(f"GUARDADO {filename} en HF Hub: {REPO_ID}") except Exception as e: logger.error(f"Error al guardar {filename} en HF: {e}") save_to_disk(ds, filename) def load_from_disk(filename, fallback=None): filepath = os.path.join(DATA_DIR, f"{filename}.json") if os.path.exists(filepath): with open(filepath, 'r') as f: data = json.load(f) return Dataset.from_list(data) if data else Dataset.from_list(fallback or []) return Dataset.from_list(fallback or []) def save_to_disk(ds, filename): filepath = os.path.join(DATA_DIR, f"{filename}.json") with open(filepath, 'w') as f: json.dump(ds.to_list(), f, indent=2) COURSES_DATA = [] CLASSROOMS_DATA = [] courses_ds = load_from_hub("courses", COURSES_DATA) classrooms_ds = load_from_hub("classrooms", CLASSROOMS_DATA) users_ds = load_from_hub("users", []) progress_ds = load_from_hub("progress", []) QUIZZES_DATA = [] QUIZ_RESPONSES_DATA = [] quizzes_ds = load_from_hub("quizzes", QUIZZES_DATA) quiz_responses_ds = load_from_hub("quiz_responses", QUIZ_RESPONSES_DATA) def get_courses_list(): return courses_ds.to_list() def save_courses_list(data): global courses_ds courses_ds = Dataset.from_list(data) save_to_hub(courses_ds, "courses") def get_classrooms_list(): return classrooms_ds.to_list() def save_classrooms_list(data): global classrooms_ds classrooms_ds = Dataset.from_list(data) save_to_hub(classrooms_ds, "classrooms") def get_users_list(): return users_ds.to_list() def save_users_list(data): global users_ds users_ds = Dataset.from_list(data) save_to_hub(users_ds, "users") def get_progress_list(): return progress_ds.to_list() def save_progress_list(data): global progress_ds progress_ds = Dataset.from_list(data) save_to_hub(progress_ds, "progress") def get_quizzes_list(): return quizzes_ds.to_list() def save_quizzes_list(data): global quizzes_ds quizzes_ds = Dataset.from_list(data) save_to_hub(quizzes_ds, "quizzes") def get_quiz_responses_list(): return quiz_responses_ds.to_list() def save_quiz_responses_list(data): global quiz_responses_ds quiz_responses_ds = Dataset.from_list(data) save_to_hub(quiz_responses_ds, "quiz_responses") @app.route('/') def index(): return render_template('index.html') @app.route('/api/login', methods=['POST']) def login(): data = request.json role = data.get('role') password = data.get('password') name = data.get('name') course_id = data.get('course_id') users = get_users_list() if role == 'teacher': from app.config import AuthConfig if password == AuthConfig.MASTER_PASSWORD: session['user'] = {'role': 'teacher', 'name': 'Maestro'} return jsonify({'success': True, 'role': 'teacher', 'name': 'Maestro'}) return jsonify({'success': False, 'error': 'Contraseña incorrecta'}), 401 elif role == 'student': if not name or not course_id: return jsonify({'success': False, 'error': 'Nombre y aula requeridos'}), 400 name = name.strip().lower() if len(name) < 2: return jsonify({'success': False, 'error': 'El nombre debe tener al menos 2 caracteres'}), 400 classrooms = get_classrooms_list() classroom = next((c for c in classrooms if c['id'] == int(course_id)), None) if not classroom: return jsonify({'success': False, 'error': 'Aula no encontrada'}), 400 enrolled = json.loads(classroom.get('enrolled_students', '[]')) existing_student = next((s for s in enrolled if s.get('name', '').lower() == name.lower()), None) if existing_student: return jsonify({'success': False, 'error': 'Ya existe un estudiante con ese nombre en esta aula'}), 400 users = get_users_list() existing_user = next((u for u in users if u.get('name', '').lower() == name.lower() and u.get('classroom_id') == int(course_id)), None) if existing_user: return jsonify({'success': False, 'error': 'Ya existe un estudiante con ese nombre en esta aula'}), 400 student_id = str(uuid.uuid4())[:8] new_user = { 'id': student_id, 'name': name, 'role': 'student', 'classroom_id': int(course_id), 'classroom_name': classroom.get('name'), 'created_at': datetime.now().isoformat() } users.append(new_user) save_users_list(users) enrolled.append({'id': student_id, 'name': name}) classroom['enrolled_students'] = json.dumps(enrolled) classroom['students'] = classroom.get('students', 0) + 1 save_classrooms_list(classrooms) session['user'] = {'role': 'student', 'id': student_id, 'name': name, 'course_id': int(course_id), 'classroom_name': classroom.get('name')} return jsonify({'success': True, 'role': 'student', 'name': name, 'student_id': student_id, 'classroom_name': classroom.get('name')}) return jsonify({'success': False, 'error': 'Rol inválido'}), 400 @app.route('/api/logout', methods=['POST']) def logout(): session.clear() return jsonify({'success': True}) @app.route('/api/session') def get_session(): if 'user' in session: return jsonify({'authenticated': True, 'user': session['user']}) return jsonify({'authenticated': False}) @app.route('/api/stats') def get_stats(): courses = get_courses_list() classrooms = get_classrooms_list() users = get_users_list() progress = get_progress_list() students = [u for u in users if u.get('role') == 'student'] completion_rate = 0 if progress: completed = sum(1 for p in progress if p.get('completed', False)) if completed > 0: completion_rate = int((completed / len(progress)) * 100) return jsonify({ "courses": len(courses), "classrooms": len(classrooms), "students": len(students), "completionRate": completion_rate }) @app.route('/api/courses') def get_courses_api(): limit = request.args.get('limit', type=int) courses = get_courses_list() if limit: courses = courses[:limit] return jsonify(courses) @app.route('/api/courses', methods=['POST']) def create_course(): data = request.json icons = {"ai": "🤖", "ml": "📊", "arduino": "⚡", "iot": "📡", "raspberry": "🖥️", "python": "🐍", "data": "📈"} colors = {"ai": "linear-gradient(135deg, #4F46E5 0%, #7C3AED 100%)", "ml": "linear-gradient(135deg, #10B981 0%, #34D399 100%)", "arduino": "linear-gradient(135deg, #00979D 0%, #4ECDC4 100%)", "iot": "linear-gradient(135deg, #F59E0B 0%, #FBBF24 100%)", "raspberry": "linear-gradient(135deg, #C13584 0%, #E1306C 100%)"} courses = get_courses_list() new_id = max([c['id'] for c in courses], default=0) + 1 new_course = { "id": new_id, "title": data.get('title'), "description": data.get('description'), "category": data.get('category'), "level": data.get('level'), "students": 0, "modules": 1, "tools": json.dumps(data.get('tools', [])), "icon": icons.get(data.get('category'), "📚"), "color": colors.get(data.get('category'), "linear-gradient(135deg, #4F46E5 0%, #7C3AED 100%)") } courses.append(new_course) save_courses_list(courses) return jsonify(new_course), 201 @app.route('/api/courses/', methods=['GET']) def get_course_detail(course_id): courses = get_courses_list() course = next((c for c in courses if c['id'] == course_id), None) if not course: return jsonify({'error': 'Curso no encontrado'}), 404 return jsonify(course) @app.route('/api/courses/', methods=['PUT']) def update_course(course_id): data = request.json courses = get_courses_list() for course in courses: if course['id'] == course_id: if 'title' in data: course['title'] = data['title'] if 'description' in data: course['description'] = data['description'] if 'tools_config' in data: course['tools_config'] = json.dumps(data['tools_config']) break save_courses_list(courses) return jsonify({'success': True}) @app.route('/api/courses//tools-config', methods=['GET']) def get_course_tools_config(course_id): courses = get_courses_list() course = next((c for c in courses if c['id'] == course_id), None) if not course: return jsonify({'error': 'Curso no encontrado'}), 404 tools_config = json.loads(course.get('tools_config', '[]')) return jsonify(tools_config) @app.route('/api/courses//tools-config', methods=['POST']) def save_course_tools_config(course_id): data = request.json courses = get_courses_list() for course in courses: if course['id'] == course_id: course['tools_config'] = json.dumps(data.get('tools_config', [])) break save_courses_list(courses) return jsonify({'success': True}) @app.route('/api/classrooms') def get_classrooms_api(): return jsonify(get_classrooms_list()) @app.route('/api/classrooms//students') def get_classroom_students(classroom_id): classrooms = get_classrooms_list() classroom = next((c for c in classrooms if c['id'] == classroom_id), None) if not classroom: return jsonify({'error': 'Aula no encontrada'}), 404 import json enrolled = json.loads(classroom.get('enrolled_students', '[]')) return jsonify(enrolled) @app.route('/api/courses/', methods=['DELETE']) def delete_course(course_id): courses = get_courses_list() courses = [c for c in courses if c['id'] != course_id] save_courses_list(courses) classrooms = get_classrooms_list() classrooms = [c for c in classrooms if c['course_id'] != course_id] save_classrooms_list(classrooms) return jsonify({'success': True}) @app.route('/api/classrooms/', methods=['DELETE']) def delete_classroom(classroom_id): classrooms = get_classrooms_list() classrooms = [c for c in classrooms if c['id'] != classroom_id] save_classrooms_list(classrooms) return jsonify({'success': True}) @app.route('/api/classroom//courses', methods=['GET']) def get_classroom_courses(classroom_id): classrooms = get_classrooms_list() classroom = next((c for c in classrooms if c['id'] == classroom_id), None) if not classroom: return jsonify({'error': 'Aula no encontrada'}), 404 course_ids = json.loads(classroom.get('course_ids', '[]')) active_courses = json.loads(classroom.get('active_courses', '[]')) student_fill_status = json.loads(classroom.get('student_fill_status', '{}')) courses = get_courses_list() classroom_courses = [] for c in courses: if c['id'] in course_ids: c_copy = dict(c) c_copy['active'] = c['id'] in active_courses fill_info = student_fill_status.get(str(c['id']), {'enabled': False, 'filled_at': None}) c_copy['student_fill_enabled'] = fill_info.get('enabled', False) c_copy['student_filled_at'] = fill_info.get('filled_at') c_copy['student_filled_by'] = fill_info.get('filled_by') classroom_courses.append(c_copy) return jsonify(classroom_courses) @app.route('/api/classroom//courses', methods=['POST']) def add_course_to_classroom(classroom_id): data = request.json course_id = int(data.get('course_id')) classrooms = get_classrooms_list() classroom = next((c for c in classrooms if c['id'] == classroom_id), None) if not classroom: return jsonify({'error': 'Aula no encontrada'}), 404 course_ids = json.loads(classroom.get('course_ids', '[]')) if course_id not in course_ids: course_ids.append(course_id) classroom['course_ids'] = json.dumps(course_ids) save_classrooms_list(classrooms) return jsonify({'success': True}) @app.route('/api/classroom//course//toggle', methods=['POST']) def toggle_classroom_course(classroom_id, course_id): classrooms = get_classrooms_list() classroom = next((c for c in classrooms if c['id'] == classroom_id), None) if not classroom: return jsonify({'error': 'Aula no encontrada'}), 404 active_courses = json.loads(classroom.get('active_courses', '[]')) if course_id in active_courses: active_courses.remove(course_id) else: active_courses.append(course_id) classroom['active_courses'] = json.dumps(active_courses) save_classrooms_list(classrooms) return jsonify({'success': True, 'active': course_id in active_courses}) @app.route('/api/classroom//course//student-fill', methods=['POST']) def toggle_student_fill(classroom_id, course_id): data = request.json enabled = data.get('enabled', True) classrooms = get_classrooms_list() classroom = next((c for c in classrooms if c['id'] == classroom_id), None) if not classroom: return jsonify({'error': 'Aula no encontrada'}), 404 student_fill_status = json.loads(classroom.get('student_fill_status', '{}')) if str(course_id) not in student_fill_status: student_fill_status[str(course_id)] = {} student_fill_status[str(course_id)]['enabled'] = enabled classroom['student_fill_status'] = json.dumps(student_fill_status) save_classrooms_list(classrooms) return jsonify({'success': True, 'enabled': enabled}) @app.route('/api/classroom//course//student-fill', methods=['GET']) def get_student_fill_status(classroom_id, course_id): classrooms = get_classrooms_list() classroom = next((c for c in classrooms if c['id'] == classroom_id), None) if not classroom: return jsonify({'error': 'Aula no encontrada'}), 404 student_fill_status = json.loads(classroom.get('student_fill_status', '{}')) fill_info = student_fill_status.get(str(course_id), {'enabled': False, 'filled_at': None}) return jsonify(fill_info) @app.route('/api/classroom//course//submit', methods=['POST']) def submit_student_course(classroom_id, course_id): student_id = request.json.get('student_id') if not student_id: return jsonify({'error': 'Estudiante no especificado'}), 400 classrooms = get_classrooms_list() classroom = next((c for c in classrooms if c['id'] == classroom_id), None) if not classroom: return jsonify({'error': 'Aula no encontrada'}), 404 student_fill_status = json.loads(classroom.get('student_fill_status', '{}')) if str(course_id) not in student_fill_status: student_fill_status[str(course_id)] = {} fill_info = student_fill_status[str(course_id)] if not fill_info.get('enabled', False): return jsonify({'error': 'El llenado de este módulo está desactivado'}), 400 if fill_info.get('filled_at'): return jsonify({'error': 'Ya has completado este módulo'}), 400 fill_info['filled_at'] = datetime.now().isoformat() fill_info['filled_by'] = student_id student_fill_status[str(course_id)] = fill_info classroom['student_fill_status'] = json.dumps(student_fill_status) save_classrooms_list(classrooms) return jsonify({'success': True, 'filled_at': fill_info['filled_at']}) @app.route('/api/classroom//courses/', methods=['DELETE']) def remove_course_from_classroom(classroom_id, course_id): classrooms = get_classrooms_list() classroom = next((c for c in classrooms if c['id'] == classroom_id), None) if not classroom: return jsonify({'error': 'Aula no encontrada'}), 404 course_ids = json.loads(classroom.get('course_ids', '[]')) if course_id in course_ids: course_ids.remove(course_id) classroom['course_ids'] = json.dumps(course_ids) save_classrooms_list(classrooms) return jsonify({'success': True}) @app.route('/api/student/classroom/courses') def get_student_classroom_courses(): course_id = request.args.get('course_id', type=int) if not course_id: return jsonify([]) courses = get_courses_list() course = next((c for c in courses if c['id'] == course_id), None) if not course: return jsonify([]) quizzes = get_quizzes_list() course_quizzes = [q for q in quizzes if q.get('course_id') == course_id] responses = get_quiz_responses_list() student_id = request.args.get('student_id') result = { 'id': course['id'], 'title': course['title'], 'description': course.get('description', ''), 'tools': json.loads(course.get('tools', '[]')), 'quizzes': [] } for quiz in course_quizzes: student_response = next((r for r in responses if r.get('quiz_id') == quiz['id'] and r.get('student_id') == student_id), None) result['quizzes'].append({ 'id': quiz['id'], 'title': quiz.get('title'), 'description': quiz.get('description'), 'active': quiz.get('active', False), 'questions': json.loads(quiz.get('questions', '[]')), 'completed': student_response is not None, 'score': student_response.get('score') if student_response else None }) return jsonify(result) @app.route('/api/classrooms', methods=['POST']) def create_classroom(): data = request.json classrooms = get_classrooms_list() new_id = max([c['id'] for c in classrooms], default=0) + 1 new_classroom = { "id": new_id, "name": data.get('name'), "description": data.get('description', ''), "capacity": data.get('capacity', 30), "students": 0, "schedule": "Por definir", "course_ids": json.dumps(data.get('course_ids', [])), "enrolled_students": json.dumps([]) } classrooms.append(new_classroom) save_classrooms_list(classrooms) return jsonify(new_classroom), 201 @app.route('/api/progress', methods=['GET']) def get_progress_api(): student_id = request.args.get('student_id') progress = get_progress_list() if student_id: progress = [p for p in progress if p.get('student_id') == student_id] return jsonify(progress) @app.route('/api/course/tools') def get_course_tools(): course_id = request.args.get('course_id', type=int) student_id = request.args.get('student_id') courses = get_courses_list() course = next((c for c in courses if c['id'] == course_id), None) if not course: return jsonify([]) from app.config import ToolConfig course_tools = json.loads(course.get('tools', '[]')) if not course_tools: all_tools = ToolConfig.get_tools_for_classroom() course_tools = [t['id'] for t in all_tools] progress = get_progress_list() result = [] for tool_id in course_tools: tool = ToolConfig.get_tool(tool_id) if tool: tool_progress = next((p for p in progress if p.get('student_id') == student_id and p.get('tool_id') == tool_id), None) result.append({ 'id': tool['id'], 'name': tool['name'], 'description': tool['description'], 'icon': tool['icon'], 'color': tool['color'], 'tags': tool['tags'], 'page': tool['page'], 'completed': tool_progress.get('completed', False) if tool_progress else False, 'best_score': tool_progress.get('best_score', 0) if tool_progress else 0, 'attempts': tool_progress.get('attempts', 0) if tool_progress else 0 }) return jsonify(result) @app.route('/api/progress', methods=['POST']) def update_progress(): data = request.json student_id = data.get('student_id') course_id = data.get('course_id') if course_id: progress = get_progress_list() existing = next((p for p in progress if p.get('student_id') == student_id and p.get('course_id') == course_id), None) if existing: existing['status'] = data.get('status', existing.get('status', 'pending')) if data.get('completed_at'): existing['completed_at'] = data.get('completed_at') else: progress.append({ 'student_id': student_id, 'course_id': course_id, 'status': data.get('status', 'pending'), 'completed_at': data.get('completed_at'), 'created_at': datetime.now().isoformat() }) save_progress_list(progress) return jsonify({'success': True}) tool_id = data.get('tool_id') score = data.get('score', 0) completed = data.get('completed', False) attempts = data.get('attempts', 1) progress = get_progress_list() existing = next((p for p in progress if p.get('student_id') == student_id and p.get('tool_id') == tool_id), None) if existing: existing['attempts'] = existing.get('attempts', 0) + 1 existing['best_score'] = max(existing.get('best_score', 0), score) if completed: existing['completed'] = True if 'correct_answers' in data: existing['correct_answers'] = data.get('correct_answers') if 'total_questions' in data: existing['total_questions'] = data.get('total_questions') else: progress.append({ 'student_id': student_id, 'tool_id': tool_id, 'name': data.get('name', ''), 'course_id': data.get('course_id'), 'attempts': attempts, 'best_score': score, 'completed': completed, 'extra_attempts': 0, 'correct_answers': data.get('correct_answers', 0), 'total_questions': data.get('total_questions', 0) }) save_progress_list(progress) return jsonify({'success': True}) @app.route('/api/progress/extra-attempts', methods=['POST']) def add_extra_attempts(): data = request.json student_id = data.get('student_id') tool_id = data.get('tool_id') extra = data.get('extra', 1) progress = get_progress_list() existing = next((p for p in progress if p.get('student_id') == student_id and p.get('tool_id') == tool_id), None) if existing: existing['extra_attempts'] = existing.get('extra_attempts', 0) + extra save_progress_list(progress) return jsonify({'success': True, 'extra_attempts': existing['extra_attempts']}) return jsonify({'success': False, 'error': 'Estudiante o actividad no encontrada'}), 404 @app.route('/api/students/progress') def get_all_students_progress(): progress = get_progress_list() users = get_users_list() courses = get_courses_list() student_ids = set(p.get('student_id') for p in progress) result = [] for student_id in student_ids: student_progs = [p for p in progress if p.get('student_id') == student_id] student = next((u for u in users if u.get('id') == student_id), None) if student_progs and student: first = student_progs[0] course = next((c for c in courses if c['id'] == first.get('course_id')), None) activities = [] total = len(student_progs) completed = sum(1 for p in student_progs if p.get('completed')) for p in student_progs: activities.append({ 'tool_id': p.get('tool_id'), 'attempts': p.get('attempts', 0), 'extra_attempts': p.get('extra_attempts', 0), 'best_score': p.get('best_score', 0), 'completed': p.get('completed', False) }) result.append({ 'id': student_id, 'name': student.get('name', student_id), 'course': course['title'] if course else 'Sin curso', 'course_id': first.get('course_id'), 'activities': activities, 'total_activities': total, 'completed_activities': completed, 'progress_percent': int((completed / total) * 100) if total > 0 else 0 }) return jsonify(result) @app.route('/api/students') def get_all_students(): users = get_users_list() courses = get_courses_list() students = [u for u in users if u.get('role') == 'student'] result = [] for student in students: course = next((c for c in courses if c['id'] == student.get('course_id')), None) result.append({ 'id': student.get('id'), 'name': student.get('name'), 'course': course['title'] if course else 'Sin curso', 'course_id': student.get('course_id'), 'created_at': student.get('created_at') }) return jsonify(result) @app.route('/api/tools') def get_tools(): from app.config import ToolConfig tools = ToolConfig.get_tools_for_classroom() return jsonify(tools) return jsonify(ToolConfig.get_tools_for_classroom()) @app.route('/api/quiz/questions') def get_quiz_questions(): return jsonify([ {"question": "¿Qué es el Machine Learning?", "options": ["Un lenguaje de programación", "Un tipo de IA que permite a las máquinas aprender de datos", "Un sistema operativo", "Un tipo de hardware"], "correct": 1}, {"question": "¿Cuál de estos es un algoritmo de clasificación?", "options": ["Regresión lineal", "K-Means", "K-Nearest Neighbors", "PCA"], "correct": 2}, {"question": "¿Qué es una red neuronal convolucional (CNN)?", "options": ["Un tipo de base de datos", "Una red usada principalmente para procesamiento de imágenes", "Un protocolo de red", "Un lenguaje de programación"], "correct": 1}, {"question": "¿Qué es el sobreajuste (overfitting) en ML?", "options": ["Cuando el modelo funciona mal con datos nuevos", "Cuando el modelo aprende perfectamente los datos de entrenamiento", "Cuando hay pocos datos de entrenamiento", "Cuando el modelo es muy simple"], "correct": 1}, {"question": "¿Qué es IoT?", "options": ["Internet de las cosas", "Interfaz de usuario", "International Organization", "Input Output Transfer"], "correct": 0}, {"question": "¿Cuál es la función de un sensor en un proyecto IoT?", "options": ["Almacenar datos", "Capturar información del entorno físico", "Procesar imágenes", "Conectar a internet"], "correct": 1} ]) @app.route('/api/raspberry/guide') def get_raspberry_guide(): return jsonify([ {"title": "Introducción a Raspberry Pi", "description": "Raspberry Pi es una computadora del tamaño de una tarjeta de crédito. Es capaz de ejecutar sistemas operativos Linux y se utiliza para proyectos de educación, automatización y domótica."}, {"title": "Instalación del Sistema Operativo", "description": "Descarga Raspberry Pi Imager e instala Raspberry OS en una tarjeta SD.", "code": "sudo apt update\nsudo apt upgrade\nraspi-config"}, {"title": "Programación con Python", "description": "Raspberry Pi viene con Python preinstalado. Empieza a programar directamente.", "code": 'print("¡Hola desde Raspberry Pi!")\n\nimport RPi.GPIO as GPIO\nGPIO.setmode(GPIO.BCM)\nGPIO.setup(18, GPIO.OUT)'}, {"title": "Proyectos Prácticos", "description": "Algunos proyectos que puedes hacer:"}, {"title": "Acceso Remoto", "description": "Configura SSH y VNC para acceder remotamente.", "code": "ssh pi@192.168.1.100\n# Contraseña: raspberry"} ]) @app.route('/api/quizzes', methods=['GET']) def get_quizzes_api(): course_id = request.args.get('course_id', type=int) quizzes = get_quizzes_list() if course_id: quizzes = [q for q in quizzes if q.get('course_id') == course_id] return jsonify(quizzes) @app.route('/api/quizzes', methods=['POST']) def create_quiz(): data = request.json quizzes = get_quizzes_list() new_id = max([q['id'] for q in quizzes], default=0) + 1 new_quiz = { "id": new_id, "title": data.get('title'), "description": data.get('description', ''), "course_id": int(data.get('course_id')), "questions": json.dumps(data.get('questions', [])), "active": False, "created_at": datetime.now().isoformat() } quizzes.append(new_quiz) save_quizzes_list(quizzes) return jsonify(new_quiz), 201 @app.route('/api/quizzes/', methods=['PUT']) def update_quiz(quiz_id): data = request.json quizzes = get_quizzes_list() for quiz in quizzes: if quiz['id'] == quiz_id: if 'title' in data: quiz['title'] = data['title'] if 'description' in data: quiz['description'] = data['description'] if 'questions' in data: quiz['questions'] = json.dumps(data['questions']) if 'active' in data: quiz['active'] = data['active'] break save_quizzes_list(quizzes) return jsonify({'success': True}) @app.route('/api/quizzes/', methods=['DELETE']) def delete_quiz(quiz_id): quizzes = get_quizzes_list() quizzes = [q for q in quizzes if q['id'] != quiz_id] save_quizzes_list(quizzes) responses = get_quiz_responses_list() responses = [r for r in responses if r.get('quiz_id') != quiz_id] save_quiz_responses_list(responses) return jsonify({'success': True}) @app.route('/api/quizzes//toggle', methods=['POST']) def toggle_quiz(quiz_id): quizzes = get_quizzes_list() for quiz in quizzes: if quiz['id'] == quiz_id: quiz['active'] = not quiz.get('active', False) break save_quizzes_list(quizzes) return jsonify({'success': True}) @app.route('/api/student/quizzes') def get_student_quizzes(): student_id = request.args.get('student_id') course_id = request.args.get('course_id', type=int) quizzes = get_quizzes_list() course_quizzes = [q for q in quizzes if q.get('course_id') == course_id] responses = get_quiz_responses_list() result = [] for quiz in course_quizzes: student_response = next((r for r in responses if r.get('quiz_id') == quiz['id'] and r.get('student_id') == student_id), None) result.append({ 'id': quiz['id'], 'title': quiz.get('title'), 'description': quiz.get('description'), 'active': quiz.get('active', False), 'questions': json.loads(quiz.get('questions', '[]')), 'completed': student_response is not None, 'score': student_response.get('score') if student_response else None }) return jsonify(result) @app.route('/api/quizzes//submit', methods=['POST']) def submit_quiz(quiz_id): data = request.json student_id = data.get('student_id') answers = data.get('answers', []) quizzes = get_quizzes_list() quiz = next((q for q in quizzes if q['id'] == quiz_id), None) if not quiz: return jsonify({'error': 'Cuestionario no encontrado'}), 404 questions = json.loads(quiz.get('questions', '[]')) score = 0 total = len(questions) for i, question in enumerate(questions): correct_idx = question.get('correct', -1) if i < len(answers) and answers[i] == correct_idx: score += 1 responses = get_quiz_responses_list() existing = next((r for r in responses if r.get('quiz_id') == quiz_id and r.get('student_id') == student_id), None) if existing: existing['answers'] = json.dumps(answers) existing['score'] = score existing['total'] = total existing['submitted_at'] = datetime.now().isoformat() else: responses.append({ 'quiz_id': quiz_id, 'student_id': student_id, 'answers': json.dumps(answers), 'score': score, 'total': total, 'submitted_at': datetime.now().isoformat() }) save_quiz_responses_list(responses) return jsonify({'success': True, 'score': score, 'total': total}) @app.route('/api/quizzes//responses') def get_quiz_responses(quiz_id): responses = get_quiz_responses_list() quizzes = get_quizzes_list() users = get_users_list() quiz_responses = [r for r in responses if r.get('quiz_id') == quiz_id] result = [] for r in quiz_responses: student = next((u for u in users if u.get('id') == r.get('student_id')), None) result.append({ 'student_name': student.get('name') if student else 'Desconocido', 'student_id': r.get('student_id'), 'score': r.get('score', 0), 'total': r.get('total', 0), 'submitted_at': r.get('submitted_at') }) return jsonify(result) if __name__ == '__main__': port = int(os.environ.get('PORT', 7860)) app.run(host='0.0.0.0', port=port, debug=True)