ECBTI / app.py
JHONGONZALEZ's picture
Actualizar README.md y otros archivos
ce639d9
# -*- coding: utf-8 -*-
"""Simulador_POC_UNAD_ECBTI_JHON_GONZALEZ.ipynb
Automatically generated by Colab.
Original file is located at
https://colab.research.google.com/drive/18fQIhC6QTMEcoQY202hGIb2BH220X-PJ
"""
import pandas as pd
import ipywidgets as widgets
from IPython.display import display, clear_output, Markdown, Image
from google.colab import files
import os
# ============================
# Información de Autoría
# ============================
AUTHOR_INFO = """
**Autor:** Jhon Fredy González
**Programa:** ECBTI (Escuela de Ciencias Básicas, Tecnologías e Ingenierías)
**Institución:** UNAD (Universidad Nacional Abierta y a Distancia)
**Centro:** CIP (Centro de Innovación y Productividad) Dosquebradas
"""
LOGO_PATH = "/content/LOGOCIPDOQUEBRADAS.png"
# ============================
# Título y Descripción
# ============================
TITLE = "# 📚 Simulador de Pruebas UNAD 📚\n\n## **Exclusivamente para Directores de Curso**"
DESCRIPTION = """
Este simulador está diseñado para ayudar a los directores de curso a probar y validar preguntas en el formato F-7-4-2 de la UNAD.
Por favor, complete el formulario de registro antes de continuar.
"""
# ============================
# Variables Globales
# ============================
user_data_file = "user_data.xlsx" # Archivo para guardar los datos de los usuarios
visit_counter_file = "visit_counter.txt" # Archivo para contar las visitas
visit_count = 0 # Contador de visitas inicializado en 0
PASSWORD = "9870548" # Contraseña para descargar el archivo
# ============================
# Función para Registrar Datos
# ============================
def register_user_info():
"""
Muestra un formulario para registrar información del usuario.
"""
global visit_count
# Incrementar el contador de visitas
visit_count += 1
save_visit_counter()
display(Markdown("### Formulario de Registro"))
# Crear widgets para el registro
name_input = widgets.Text(description="Nombre:")
document_input = widgets.Text(description="Documento:")
course_code_input = widgets.Text(description="Código Curso:")
course_name_input = widgets.Text(description="Nombre Curso:")
evaluation_type_input = widgets.Text(description="Tipo Evaluación:")
submit_button = widgets.Button(description="Enviar Registro ✅")
def on_submit_clicked(b):
# Guardar la información ingresada
global user_info
user_info = {
"Nombre": name_input.value,
"Documento": document_input.value,
"Código Curso": course_code_input.value,
"Nombre Curso": course_name_input.value,
"Tipo Evaluación": evaluation_type_input.value
}
# Guardar los datos del usuario en un archivo Excel
save_user_data(user_info)
# Limpiar la salida y mostrar mensaje de confirmación
clear_output(wait=True)
display(Markdown(f"### ¡Registro Exitoso! ✅"))
display(Markdown(f"- **Nombre:** {user_info['Nombre']}"))
display(Markdown(f"- **Documento:** {user_info['Documento']}"))
display(Markdown(f"- **Código Curso:** {user_info['Código Curso']}"))
display(Markdown(f"- **Nombre Curso:** {user_info['Nombre Curso']}"))
display(Markdown(f"- **Tipo Evaluación:** {user_info['Tipo Evaluación']}"))
# Mostrar el número de la visita
display(Markdown(f"--- Esta es la **visita número {visit_count}** ---"))
# Continuar con el quiz
questions = load_questions_from_excel()
display_question(0, questions)
# Mostrar los widgets y el botón
display(name_input)
display(document_input)
display(course_code_input)
display(course_name_input)
display(evaluation_type_input)
display(submit_button)
# Asociar la función al botón
submit_button.on_click(on_submit_clicked)
# ============================
# Funciones Principales
# ============================
def load_questions_from_excel():
"""
Permite al usuario cargar un archivo Excel y lo convierte en una lista de preguntas.
"""
display(Markdown("**Nota Importante:**"))
display(Markdown("- El archivo Excel **debe estar en el formato F-7-4-2 de la UNAD**."))
display(Markdown("- **No debe incluir encabezados** y debe tener al menos 16 columnas."))
display(Markdown("- El archivo debe estar en formato **`.xlsx`**, no en formato `.xls` (versión antigua de Excel)."))
print("Cargando archivo Excel...")
uploaded = files.upload()
# Verificar que se haya cargado exactamente un archivo
if len(uploaded.keys()) != 1:
raise ValueError("Debe cargar exactamente un archivo.")
filename = list(uploaded.keys())[0]
try:
# Validar que el archivo tenga la extensión .xlsx
if not filename.endswith(".xlsx"):
raise ValueError("El archivo debe estar en formato `.xlsx`. Por favor, use un archivo moderno de Excel.")
# Leer el archivo Excel sin encabezados
df = pd.read_excel(filename, header=None)
# Eliminar el archivo cargado para liberar espacio
os.remove(filename)
# Verificar que el archivo tenga suficientes columnas
if df.shape[1] < 16:
raise ValueError("El archivo no tiene el formato esperado. Debe tener al menos 16 columnas.")
# Seleccionar solo las columnas relevantes (usando índices numéricos)
df = df.iloc[:, [5, 6, 7, 8, 9, 10, 11, 12, 13, 14]]
# Renombrar columnas para facilitar el acceso
df.columns = [
"ENUNCIADO",
"CLAVE",
"OPCIÓN_DE_RESPUESTA_A",
"REALIMENTACIÓN_A",
"OPCIÓN_DE_RESPUESTA_B",
"REALIMENTACIÓN_B",
"OPCIÓN_DE_RESPUESTA_C",
"REALIMENTACIÓN_C",
"OPCIÓN_DE_RESPUESTA_D",
"REALIMENTACIÓN_D"
]
# Convertir todos los valores a cadenas de texto y reemplazar NaN con cadenas vacías
df = df.astype(str).replace("nan", "")
return df.to_dict(orient="records")
except Exception as e:
# Si ocurre un error, asegurarse de eliminar el archivo cargado
if os.path.exists(filename):
os.remove(filename)
raise e
def display_question(index, questions):
"""
Muestra una pregunta específica con opciones de respuesta.
"""
global current_question_index
current_question_index = index
question = questions[index]
# Mostrar la pregunta con formato matemático
display(Markdown(f"--- Pregunta {index + 1} --- 📝"))
display(Markdown(question["ENUNCIADO"]))
display(Markdown("\n**Opciones:**"))
options = {
"A": question["OPCIÓN_DE_RESPUESTA_A"],
"B": question["OPCIÓN_DE_RESPUESTA_B"],
"C": question["OPCIÓN_DE_RESPUESTA_C"],
"D": question["OPCIÓN_DE_RESPUESTA_D"]
}
for key, value in options.items():
if value.strip(): # Solo mostrar opciones no vacías
display(Markdown(f"{key}) {value}"))
# Widget para seleccionar la respuesta
radio_buttons = widgets.RadioButtons(
options=["A", "B", "C", "D"],
description="Respuesta:",
disabled=False
)
submit_button = widgets.Button(description="Enviar Respuesta ✅")
def on_submit_clicked(b):
selected_answer = radio_buttons.value
correct_answer = question["CLAVE"]
# Obtener la retroalimentación correspondiente
feedback = ""
if selected_answer == "A":
feedback = question["REALIMENTACIÓN_A"]
elif selected_answer == "B":
feedback = question["REALIMENTACIÓN_B"]
elif selected_answer == "C":
feedback = question["REALIMENTACIÓN_C"]
elif selected_answer == "D":
feedback = question["REALIMENTACIÓN_D"]
# Mostrar retroalimentación
clear_output(wait=True)
if selected_answer == correct_answer:
display(Markdown(f"\n**¡Correcto! ✅ {feedback}**"))
else:
display(Markdown(f"\n**¡Incorrecto! ❌ {feedback}**"))
# Botón para avanzar a la siguiente pregunta
if index < len(questions) - 1:
next_button = widgets.Button(description="Siguiente Pregunta ➡️")
display(next_button)
next_button.on_click(lambda b: display_question(index + 1, questions))
else:
display(Markdown("\n**🎉 ¡Has completado todas las preguntas! 🎉**"))
display(radio_buttons)
display(submit_button)
submit_button.on_click(on_submit_clicked)
# ============================
# Funciones para Guardar Datos
# ============================
def save_user_data(user_info):
"""
Guarda los datos del usuario en un archivo Excel.
"""
try:
# Si el archivo ya existe, leerlo
if os.path.exists(user_data_file):
df = pd.read_excel(user_data_file)
else:
# Crear un DataFrame vacío si el archivo no existe
df = pd.DataFrame(columns=["Nombre", "Documento", "Código Curso", "Nombre Curso", "Tipo Evaluación"])
# Agregar los nuevos datos
new_row = pd.DataFrame([user_info])
df = pd.concat([df, new_row], ignore_index=True)
# Guardar el archivo
df.to_excel(user_data_file, index=False)
except Exception as e:
print(f"Error al guardar los datos del usuario: {e}")
def save_visit_counter():
"""
Guarda el contador de visitas en un archivo de texto.
"""
global visit_count
try:
# Si el archivo ya existe, leer el contador
if os.path.exists(visit_counter_file):
with open(visit_counter_file, "r") as f:
visit_count = int(f.read().strip() or 0)
# Incrementar el contador
visit_count += 1
# Guardar el contador en el archivo
with open(visit_counter_file, "w") as f:
f.write(str(visit_count))
except Exception as e:
print(f"Error al guardar el contador de visitas: {e}")
def download_user_data_with_password():
"""
Descarga el archivo Excel con los datos de los usuarios después de verificar la contraseña.
"""
password_input = widgets.Password(description="Contraseña:")
download_button = widgets.Button(description="Descargar Registros 🔒")
output = widgets.Output()
def on_download_clicked(b):
with output:
if password_input.value == PASSWORD:
if os.path.exists(user_data_file):
files.download(user_data_file)
display(Markdown("### 📥 Archivo descargado exitosamente: `user_data.xlsx`"))
else:
display(Markdown("### ❌ No hay datos disponibles para descargar."))
else:
display(Markdown("### ❌ Contraseña incorrecta. Inténtalo de nuevo."))
download_button.on_click(on_download_clicked)
display(password_input, download_button, output)
# ============================
# Ejecución Principal
# ============================
if __name__ == "__main__":
# Mostrar el logo
display(Image(filename=LOGO_PATH, width=300))
# Mostrar la información de autoría
display(Markdown(TITLE))
display(Markdown(AUTHOR_INFO))
# Mostrar la descripción
display(Markdown(DESCRIPTION))
# Botón para descargar los datos con contraseña
display(Markdown("### Para el creador: Usa el botón a continuación para descargar los registros."))
download_user_data_with_password()
# Registrar la información del usuario
register_user_info()