Aquí tienes un guion de prompts listo para usar con Copilot en Codespaces (modo agente). Están ordenados por fases. Copia y pega cada bloque, espera el resultado, ejecuta y valida. Mantén el nombre del proyecto: rubrica-streamlit.


Fase 0 — Estructura del repo y entorno

Prompt 0.1 — Inicialización

Actúa como agente. Crea la estructura base de un proyecto Streamlit con SQLite para una app de rúbricas:
- Carpeta raíz: rubrica-streamlit
- Archivos: README.md, requirements.txt, .gitignore, app.py, db.py, utils.py
- Carpeta data/ para exportes CSV
- requirements.txt con: streamlit, pandas, pydantic
- .gitignore con: __pycache__/, .streamlit/, rubrica.db, data/*.csv
Explica cada archivo y deja comandos para ejecutar localmente.

Prompt 0.2 — README inicial

Genera un README.md con: descripción del proyecto, ejecución local (streamlit run app.py), variables configurables, y notas de despliegue en Streamlit Community Cloud y Render. Incluye una sección “Roadmap”.

Fase 1 — Modelo de datos y capa DB (SQLite)

Prompt 1.1 — Módulo db.py

Implementa db.py con una interfaz CRUD segura para SQLite:
- open_conn(path="rubrica.db"): aplica PRAGMAs WAL, synchronous=NORMAL, foreign_keys=ON
- init_db(): crea tabla evaluaciones(id, curso, evaluacion, fecha, grupo_o_estudiante, estructura, programacion, teoria, ia, reflexion, presentacion, nota_final, observaciones, created_at)
- insert_evaluacion(dict)
- list_resumen(order="DESC"): devuelve id, fecha, grupo_o_estudiante, nota_final
- list_detalle(filtro_texto=None, fecha=None, order="DESC")
- export_csv(path): exporta todo a CSV en data/
Incluye typing y manejo de errores.

Prompt 1.2 — Datos de ejemplo

Agrega en db.py una función seed_demo() que inserte 5 registros de ejemplo para pruebas. No se ejecuta por defecto, sólo cuando se llama explícitamente desde app.py con un checkbox “Cargar datos demo”.

Fase 2 — Utilidades y pesos de la rúbrica

Prompt 2.1 — Módulo utils.py

Implementa utils.py con:
- PESOS dict: estructura=15, programacion=20, teoria=15, ia=10, reflexion=15, presentacion=25
- DESCRIPCIONES dict legibles
- validate_notas(notas: dict) -> None: valida rango 1–5
- nota_final(notas: dict, pesos=PESOS) -> float: redondeo 2 decimales
- niveles_texto(): retorna “1=Deficiente · 2=Básico · 3=Aceptable · 4=Bueno · 5=Excelente”
Incluye pruebas unitarias simples dentro de un bloque `if __name__ == "__main__":` (asserts).

Fase 3 — Interfaz Streamlit

Prompt 3.1 — app.py: Layout y Sidebar

En app.py:
- set_page_config(title, icon, layout="wide")
- CSS simple para cabecera con logo (URL parametrizable) y card styling
- Sidebar: curso, evaluación, fecha (por defecto hoy), lista editable de grupos/estudiantes (textarea, 1 por línea), modo de almacenamiento (“SQLite” o “Sólo CSV”), botón “Cargar datos demo” que llama a seed_demo()
- Llama a init_db() al inicio

Prompt 3.2 — app.py: Calificación en vivo

En app.py agrega panel principal:
- Selectbox de grupo/estudiante a partir del roster del sidebar
- Seis sliders (1–5, step 0.5) con etiquetas y pesos desde DESCRIPCIONES/PESOS
- Cálculo en vivo de nota final con utils.nota_final()
- Text area de observaciones
- Botón “Guardar evaluación”
Lógica:
- Si modo “SQLite”: usar db.insert_evaluacion(); éxito con st.success
- Si modo “Sólo CSV”: acumular en un df de sesión (st.session_state) y ofrecer descarga CSV inmediata
Incluye validación con utils.validate_notas.

Prompt 3.3 — app.py: Reporte y exportes

Añade dos secciones:
1) “Reporte” a la derecha: tabla de resumen (id, fecha, grupo_o_estudiante, nota_final). Botón para descargar CSV de resumen.
2) “Detalle y filtros”: filtros por texto (LIKE) y fecha exacta, muestra tabla completa. Botón para descargar CSV detallado.
Si el modo es “Sólo CSV”, operar sobre el df en memoria; si es “SQLite”, consultar db.list_resumen y db.list_detalle.

Prompt 3.4 — app.py: Ayuda y accesos

Agrega un expander “Ayuda rápida” con pasos de uso en clase. 
Agrega control de acceso básico opcional: text_input password comparado con st.secrets["EVAL_KEY"] (si existe). Si no coincide, st.stop() con mensaje.

Fase 4 — Pruebas locales y QA

Prompt 4.1 — Run y QA

Indica comandos para ejecutar localmente:
- pip install -r requirements.txt
- streamlit run app.py
Incluye lista de pruebas manuales:
1) Guardar evaluación SQLite y verla en resumen/detalle
2) Modo “Sólo CSV”: agregar filas y descargar
3) Filtros por texto y fecha
4) Carga de datos demo
5) Validación de rangos (notas fuera de 1–5)
6) Export CSV desde SQLite y desde memoria
Redacta un checklist de QA en README.

Fase 5 — Despliegue

Prompt 5.1 — Streamlit Community Cloud

Agrega instrucciones en README para desplegar en Streamlit Community Cloud:
- Conectar repo
- Variables en Secrets (opcional): EVAL_KEY
- Límite de “sleep” y cómo reactivar
Crea un archivo .streamlit/config.toml con un tema claro y ancho “wide”.

Prompt 5.2 — Render (24/7 recomendado)

Agrega una sección en README: despliegue en Render como web service:
- Dockerfile mínimo o usar build command: pip install -r requirements.txt
- Start command: streamlit run app.py --server.port $PORT --server.address 0.0.0.0
Incluye nota sobre costo aproximado y persistencia del archivo rubrica.db.

Fase 6 — Versión multiprograma (opcional)

Prompt 6.1 — Selector de plantilla de rúbrica

Generaliza la app para múltiples programas:
- En sidebar, selectbox “Plantilla de rúbrica”: {Agroindustrial, Civil, Estadística}
- Cambia PESOS y DESCRIPCIONES en utils según plantilla seleccionada (define tres dicts y un factory).
- Guarda el nombre de plantilla en la tabla (columna “curso” o “plantilla”).

Fase 7 — Seguridad y backups

Prompt 7.1 — Backups automáticos

Implementa en db.py una función backup_csv_timestamp() que exporte la tabla completa a data/backup_YYYYMMDD_HHMMSS.csv. 
En app.py, agrega un botón “Backup CSV” en la sección de detalle que llama a esa función cuando modo=SQLite.

Fase 8 — Pulido final

Prompt 8.1 — Accesibilidad y estilo

Mejora la accesibilidad:
- Etiquetas cortas y descriptivas
- Placeholders en inputs
- Mensajes de error claros
Agrega contador de registros y promedio de nota final en la parte superior del reporte.

Notas operativas

  • Ejecuta cada prompt secuencialmente.
  • Pide a Copilot “mostrar el diff” antes de confirmar cambios en archivos sensibles.
  • Tras cada fase, levanta la app (streamlit run app.py) y valida.

¿Quieres que además te deje un set de datos de prueba (CSV) y un script de carga masiva a SQLite para simular una sesión con 10–15 grupos?

Aquí tiene un set de datos de prueba (CSV) para 15 grupos y un script de carga masiva a SQLite compatible con su app rubrica-streamlit. Incluyo instrucciones de uso.

1) CSV de prueba — 15 grupos

Guárdelo como data/demo_evaluaciones_15.csv.

curso,evaluacion,fecha,grupo_o_estudiante,estructura,programacion,teoria,ia,reflexion,presentacion,observaciones
Fundamentos de Programación (Agroindustrial),Tarea Grupal — Secadores Solares,2025-11-05,Grupo 1,4.0,4.5,4.0,3.5,4.0,4.8,"Buen dominio general"
Fundamentos de Programación (Agroindustrial),Tarea Grupal — Secadores Solares,2025-11-05,Grupo 2,3.5,4.0,3.5,3.0,3.5,4.2,"Mejorar claridad en teoría"
Fundamentos de Programación (Agroindustrial),Tarea Grupal — Secadores Solares,2025-11-05,Grupo 3,4.5,4.8,4.2,4.0,4.3,4.9,"Excelente coordinación"
Fundamentos de Programación (Agroindustrial),Tarea Grupal — Secadores Solares,2025-11-05,Grupo 4,3.0,3.5,3.2,3.0,3.0,3.8,"Códigos incompletos"
Fundamentos de Programación (Agroindustrial),Tarea Grupal — Secadores Solares,2025-11-05,Grupo 5,4.2,4.0,4.0,3.5,4.1,4.6,"Buena interpretación"
Fundamentos de Programación (Agroindustrial),Tarea Grupal — Secadores Solares,2025-11-05,Grupo 6,3.8,4.1,3.9,3.5,3.7,4.3,"Prompts adecuados"
Fundamentos de Programación (Agroindustrial),Tarea Grupal — Secadores Solares,2025-11-05,Grupo 7,4.0,4.2,3.8,3.2,3.9,4.4,"Sólida exposición"
Fundamentos de Programación (Agroindustrial),Tarea Grupal — Secadores Solares,2025-11-05,Grupo 8,3.6,3.9,3.5,3.3,3.6,4.0,"Mejorar estructura"
Fundamentos de Programación (Agroindustrial),Tarea Grupal — Secadores Solares,2025-11-05,Grupo 9,4.4,4.6,4.3,3.9,4.2,4.7,"Resultados reproducibles"
Fundamentos de Programación (Agroindustrial),Tarea Grupal — Secadores Solares,2025-11-05,Grupo 10,3.2,3.7,3.4,3.1,3.3,3.9,"Falta profundizar"
Fundamentos de Programación (Agroindustrial),Tarea Grupal — Secadores Solares,2025-11-05,Grupo 11,4.1,4.2,4.0,3.6,4.0,4.5,"Buena coordinación del equipo"
Fundamentos de Programación (Agroindustrial),Tarea Grupal — Secadores Solares,2025-11-05,Grupo 12,3.7,3.8,3.6,3.2,3.5,4.1,"Documentación mejorable"
Fundamentos de Programación (Agroindustrial),Tarea Grupal — Secadores Solares,2025-11-05,Grupo 13,4.3,4.4,4.1,3.8,4.2,4.6,"Análisis sólido y claro"
Fundamentos de Programación (Agroindustrial),Tarea Grupal — Secadores Solares,2025-11-05,Grupo 14,3.4,3.6,3.3,3.0,3.2,3.8,"Presentación correcta"
Fundamentos de Programación (Agroindustrial),Tarea Grupal — Secadores Solares,2025-11-05,Grupo 15,4.6,4.7,4.3,4.1,4.3,4.9,"Excelente dominio técnico"

2) Script de carga masiva a SQLite

Guárdelo como tools/load_csv_to_sqlite.py.

"""
Carga masiva de calificaciones desde CSV a SQLite para la app rubrica-streamlit.

Uso:
  python tools/load_csv_to_sqlite.py --csv data/demo_evaluaciones_15.csv

Requisitos:
  - Archivo de base de datos: rubrica.db (se crea si no existe)
  - Estructura de tabla compatible con app.py:
    evaluaciones(
      id INTEGER PK, curso TEXT, evaluacion TEXT, fecha TEXT,
      grupo_o_estudiante TEXT, estructura REAL, programacion REAL, teoria REAL,
      ia REAL, reflexion REAL, presentacion REAL, nota_final REAL,
      observaciones TEXT, created_at TEXT
    )
"""

import argparse
import datetime as dt
import sqlite3
import pandas as pd

DB_PATH = "rubrica.db"

# Pesos coherentes con la rúbrica de la app
PESOS = {
    "estructura": 15,
    "programacion": 20,
    "teoria": 15,
    "ia": 10,
    "reflexion": 15,
    "presentacion": 25
}

def open_conn(path=DB_PATH):
    """Abre conexión SQLite con PRAGMAs para lecturas concurrentes razonables."""
    conn = sqlite3.connect(path, check_same_thread=False, timeout=30.0)
    conn.execute("PRAGMA journal_mode=WAL;")
    conn.execute("PRAGMA synchronous=NORMAL;")
    conn.execute("PRAGMA foreign_keys=ON;")
    return conn

def init_db(conn):
    """Crea la tabla si no existe (esquema compatible con la app)."""
    conn.execute("""
    CREATE TABLE IF NOT EXISTS evaluaciones (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        curso TEXT,
        evaluacion TEXT,
        fecha TEXT,
        grupo_o_estudiante TEXT,
        estructura REAL,
        programacion REAL,
        teoria REAL,
        ia REAL,
        reflexion REAL,
        presentacion REAL,
        nota_final REAL,
        observaciones TEXT,
        created_at TEXT
    )
    """)

def validar_fila(r: pd.Series):
    """Valida rangos de notas y formato de fecha ISO."""
    for k in ["estructura","programacion","teoria","ia","reflexion","presentacion"]:
        try:
            v = float(r[k])
        except Exception as e:
            raise ValueError(f"Valor no numérico en {k}: {r[k]}") from e
        if not (1.0 <= v <= 5.0):
            raise ValueError(f"Nota fuera de rango 1–5 en {k}: {v}")

    # fecha en ISO YYYY-MM-DD
    try:
        dt.date.fromisoformat(str(r["fecha"]))
    except Exception as e:
        raise ValueError(f"Fecha no ISO (YYYY-MM-DD): {r['fecha']}") from e

def calc_nota_final(r: pd.Series) -> float:
    """Calcula la nota final ponderada con dos decimales."""
    nf = (
        r["estructura"]   * PESOS["estructura"]   +
        r["programacion"] * PESOS["programacion"] +
        r["teoria"]       * PESOS["teoria"]       +
        r["ia"]           * PESOS["ia"]           +
        r["reflexion"]    * PESOS["reflexion"]    +
        r["presentacion"] * PESOS["presentacion"]
    ) / 100.0
    return round(float(nf), 2)

def insert_row(conn, r: pd.Series):
    """Inserta una fila en la tabla evaluaciones."""
    conn.execute("""
        INSERT INTO evaluaciones (
            curso, evaluacion, fecha, grupo_o_estudiante,
            estructura, programacion, teoria, ia, reflexion, presentacion,
            nota_final, observaciones, created_at
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    """, (
        str(r["curso"]),
        str(r["evaluacion"]),
        str(r["fecha"]),
        str(r["grupo_o_estudiante"]),
        float(r["estructura"]),
        float(r["programacion"]),
        float(r["teoria"]),
        float(r["ia"]),
        float(r["reflexion"]),
        float(r["presentacion"]),
        float(r["nota_final"]),
        str(r.get("observaciones", "")),
        dt.datetime.now().isoformat(timespec="seconds")
    ))

def main(csv_path: str):
    # Leer CSV
    df = pd.read_csv(csv_path)

    # Columnas requeridas
    requeridas = [
        "curso","evaluacion","fecha","grupo_o_estudiante",
        "estructura","programacion","teoria","ia","reflexion","presentacion"
    ]
    for c in requeridas:
        if c not in df.columns:
            raise ValueError(f"Falta columna requerida: {c}")

    # Calcular nota_final si no existe
    if "nota_final" not in df.columns:
        df["nota_final"] = df.apply(calc_nota_final, axis=1)

    # Validar filas
    for _, row in df.iterrows():
        validar_fila(row)

    # DB
    conn = open_conn()
    init_db(conn)

    with conn:
        for _, row in df.iterrows():
            insert_row(conn, row)

    print(f"Cargadas {len(df)} filas desde {csv_path} → {DB_PATH}")

if __name__ == "__main__":
    ap = argparse.ArgumentParser(description="Carga CSV de rúbricas a SQLite")
    ap.add_argument("--csv", required=True, help="Ruta al CSV de evaluaciones")
    args = ap.parse_args()
    main(args.csv)

3) Instrucciones rápidas

# Dentro del repo rubrica-streamlit
mkdir -p tools data

# Guarde el CSV en data/
# data/demo_evaluaciones_15.csv

# Instale dependencias (si no lo hizo antes)
pip install streamlit pandas

# Cargue a SQLite
python tools/load_csv_to_sqlite.py --csv data/demo_evaluaciones_15.csv

# Levante la app
streamlit run app.py
  • Verá los 15 registros en Reporte y Detalle y filtros.
  • Puede repetir la carga con otros CSV para simular sesiones adicionales.