from flask import Flask, render_template, jsonify, request, session from flask_cors import CORS import json import os import uuid from datetime import datetime app = Flask(__name__) app.secret_key = os.environ.get('SECRET_KEY', 'educateenIA_secret_key_2024') CORS(app) # HF Spaces proporciona /data para almacenamiento persistente DATA_DIR = os.environ.get('DATA_DIR', '/data') # Si no existe /data, usar directorio local (para desarrollo) if not os.path.exists(DATA_DIR): DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') os.makedirs(DATA_DIR, exist_ok=True) COURSES_FILE = os.path.join(DATA_DIR, 'courses.json') CLASSROOMS_FILE = os.path.join(DATA_DIR, 'classrooms.json') USERS_FILE = os.path.join(DATA_DIR, 'users.json') PROGRESS_FILE = os.path.join(DATA_DIR, 'progress.json') def init_data_files(): if not os.path.exists(COURSES_FILE): with open(COURSES_FILE, 'w') as f: json.dump([ {"id": 1, "title": "Introducción a la IA", "description": "Aprende los conceptos fundamentales de la Inteligencia Artificial", "category": "ai", "level": "beginner", "students": 45, "modules": 8, "icon": "🤖", "color": "linear-gradient(135deg, #4F46E5 0%, #7C3AED 100%)"}, {"id": 2, "title": "Machine Learning Práctico", "description": "Implementa modelos de ML desde cero con Python", "category": "ml", "level": "intermediate", "students": 32, "modules": 12, "icon": "📊", "color": "linear-gradient(135deg, #10B981 0%, #34D399 100%)"}, {"id": 3, "title": "Arduino para Principiantes", "description": "Crea tus primeros proyectos electrónicos con Arduino", "category": "arduino", "level": "beginner", "students": 56, "modules": 10, "icon": "⚡", "color": "linear-gradient(135deg, #00979D 0%, #4ECDC4 100%)"}, {"id": 4, "title": "IoT con ESP32", "description": "Conecta dispositivos a internet y crea soluciones IoT", "category": "iot", "level": "intermediate", "students": 28, "modules": 8, "icon": "📡", "color": "linear-gradient(135deg, #F59E0B 0%, #FBBF24 100%)"}, {"id": 5, "title": "Raspberry Pi Mastery", "description": "Domina Raspberry Pi para proyectos de automatización", "category": "raspberry", "level": "advanced", "students": 18, "modules": 15, "icon": "🖥️", "color": "linear-gradient(135deg, #C13584 0%, #E1306C 100%)"} ], f, indent=2) if not os.path.exists(CLASSROOMS_FILE): with open(CLASSROOMS_FILE, 'w') as f: json.dump([ {"id": 1, "name": "IA Básico - Grupo A", "course_id": 1, "course_name": "Introducción a la IA", "students": 25, "capacity": 30, "schedule": "Lun-Mié 10:00", "tools": ["ai_test", "ml_classification"], "enrolled_students": []}, {"id": 2, "name": "ML Práctico - Grupo A", "course_id": 2, "course_name": "Machine Learning Práctico", "students": 20, "capacity": 25, "schedule": "Mar-Jue 14:00", "tools": ["ml_classification", "ml_regression"], "enrolled_students": []}, {"id": 3, "name": "Arduino - Taller", "course_id": 3, "course_name": "Arduino para Principiantes", "students": 18, "capacity": 20, "schedule": "Sáb 9:00", "tools": ["arduino_sim", "iot_sim"], "enrolled_students": []} ], f, indent=2) if not os.path.exists(USERS_FILE): with open(USERS_FILE, 'w') as f: json.dump({"students": [], "teachers": []}, f, indent=2) if not os.path.exists(PROGRESS_FILE): with open(PROGRESS_FILE, 'w') as f: json.dump({}, f, indent=2) init_data_files() def load_json(filepath): with open(filepath, 'r') as f: return json.load(f) def save_json(filepath, data): with open(filepath, 'w') as f: json.dump(data, f, indent=2) @app.route('/') def index(): return render_template('index.html') # ============ AUTH ============ @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 = load_json(USERS_FILE) 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 curso requeridos'}), 400 student_id = str(uuid.uuid4())[:8] student = {'id': student_id, 'name': name, 'course_id': int(course_id), 'created_at': datetime.now().isoformat()} users['students'].append(student) save_json(USERS_FILE, users) classrooms = load_json(CLASSROOMS_FILE) for cr in classrooms: if cr['course_id'] == int(course_id): if 'enrolled_students' not in cr: cr['enrolled_students'] = [] cr['enrolled_students'].append({'id': student_id, 'name': name}) save_json(CLASSROOMS_FILE, classrooms) session['user'] = {'role': 'student', 'id': student_id, 'name': name, 'course_id': int(course_id)} return jsonify({'success': True, 'role': 'student', 'name': name, 'student_id': student_id}) 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}) # ============ STATS ============ @app.route('/api/stats') def get_stats(): courses = load_json(COURSES_FILE) classrooms = load_json(CLASSROOMS_FILE) users = load_json(USERS_FILE) progress = load_json(PROGRESS_FILE) total_students = len(users.get('students', [])) completion_rate = 0 if progress: completed = sum(1 for p in progress.values() if p.get('completed', False)) if completed > 0: completion_rate = int((completed / len(progress)) * 100) return jsonify({ "courses": len(courses), "classrooms": len(classrooms), "students": total_students, "completionRate": completion_rate }) # ============ COURSES ============ @app.route('/api/courses') def get_courses(): limit = request.args.get('limit', type=int) courses = load_json(COURSES_FILE) if limit: courses = courses[:limit] return jsonify(courses) @app.route('/api/courses', methods=['POST']) def create_course(): courses = load_json(COURSES_FILE) 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%)"} new_course = { "id": max([c['id'] for c in courses], default=0) + 1, "title": data.get('title'), "description": data.get('description'), "category": data.get('category'), "level": data.get('level'), "students": 0, "modules": 1, "icon": icons.get(data.get('category'), "📚"), "color": colors.get(data.get('category'), "linear-gradient(135deg, #4F46E5 0%, #7C3AED 100%)") } courses.append(new_course) save_json(COURSES_FILE, courses) return jsonify(new_course), 201 # ============ CLASSROOMS ============ @app.route('/api/classrooms') def get_classrooms(): return jsonify(load_json(CLASSROOMS_FILE)) @app.route('/api/classrooms', methods=['POST']) def create_classroom(): classrooms = load_json(CLASSROOMS_FILE) courses = load_json(COURSES_FILE) data = request.json course = next((c for c in courses if c['id'] == int(data.get('course_id', 0))), None) new_classroom = { "id": max([c['id'] for c in classrooms], default=0) + 1, "name": data.get('name'), "course_id": int(data.get('course_id')), "course_name": course['title'] if course else 'Sin curso', "description": data.get('description'), "capacity": data.get('capacity', 30), "students": 0, "schedule": "Por definir", "tools": data.get('tools', []), "enrolled_students": [] } classrooms.append(new_classroom) save_json(CLASSROOMS_FILE, classrooms) return jsonify(new_classroom), 201 # ============ STUDENT PROGRESS ============ @app.route('/api/progress', methods=['GET']) def get_progress(): student_id = request.args.get('student_id') progress = load_json(PROGRESS_FILE) if student_id: return jsonify(progress.get(student_id, {})) return jsonify(progress) @app.route('/api/progress', methods=['POST']) def update_progress(): data = request.json student_id = data.get('student_id') tool_id = data.get('tool_id') score = data.get('score', 0) completed = data.get('completed', False) attempts = data.get('attempts', 1) progress = load_json(PROGRESS_FILE) if student_id not in progress: progress[student_id] = {'name': data.get('name', ''), 'course_id': data.get('course_id'), 'activities': {}} if tool_id not in progress[student_id]['activities']: progress[student_id]['activities'][tool_id] = {'attempts': 0, 'best_score': 0, 'completed': False, 'extra_attempts': 0} activity = progress[student_id]['activities'][tool_id] activity['attempts'] = attempts if score > activity['best_score']: activity['best_score'] = score if completed: activity['completed'] = True save_json(PROGRESS_FILE, 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 = load_json(PROGRESS_FILE) if student_id in progress and tool_id in progress[student_id]['activities']: progress[student_id]['activities'][tool_id]['extra_attempts'] = progress[student_id]['activities'][tool_id].get('extra_attempts', 0) + extra save_json(PROGRESS_FILE, progress) return jsonify({'success': True, 'extra_attempts': progress[student_id]['activities'][tool_id]['extra_attempts']}) return jsonify({'success': False, 'error': 'Estudiante o actividad no encontrada'}), 404 @app.route('/api/students/progress') def get_all_students_progress(): progress = load_json(PROGRESS_FILE) users = load_json(USERS_FILE) courses = load_json(COURSES_FILE) result = [] for student_id, data in progress.items(): student = next((s for s in users['students'] if s['id'] == student_id), None) course = next((c for c in courses if c['id'] == data.get('course_id')), None) activities = [] total = 0 completed = 0 for tool_id, act in data.get('activities', {}).items(): total += 1 if act.get('completed'): completed += 1 activities.append({ 'tool_id': tool_id, 'attempts': act.get('attempts', 0), 'extra_attempts': act.get('extra_attempts', 0), 'best_score': act.get('best_score', 0), 'completed': act.get('completed', False) }) result.append({ 'id': student_id, 'name': data.get('name', student_id), 'course': course['title'] if course else 'Sin curso', 'course_id': data.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) # ============ TOOLS ============ @app.route('/api/tools') def get_tools(): from app.config import ToolConfig return jsonify(ToolConfig.get_tools_for_classroom()) # ============ QUIZ ============ @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} ]) # ============ RASPBERRY GUIDE ============ @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"} ]) if __name__ == '__main__': port = int(os.environ.get('PORT', 7860)) app.run(host='0.0.0.0', port=port, debug=True)