Propuesta completa: un único script de Python que:

  1. Instala extensiones de VSCode por CLI,
  2. Crea y configura .venv,
  3. Prepara .vscode/ con settings.json, extensions.json, tasks.json, launch.json,
  4. Instala streamlit y verifica configuración, y
  5. Entrega instrucciones para “resetear” la interfaz de VSCode.

1) Explicación

Función

Automatiza el arranque del entorno de trabajo en VSCode para cursos de Python con despliegue en Streamlit y uso de GitHub y Copilot. Evita errores comunes de instalación, selección de intérprete y falta de extensiones.

Estructura

  • Detección de SO y rutas.

  • Verificación de CLI de VSCode (code).

  • Instalación de extensiones: Python, Pylance, Jupyter, GitHub Pull Requests, Copilot, Copilot Chat.

  • Creación de entorno virtual .venv con venv.

  • Instalación de dependencias base: streamlit.

  • Carpeta .vscode:

    • settings.json: fija el intérprete del proyecto al de .venv y activa el entorno en la terminal.
    • extensions.json: recomendaciones para alumnos.
    • tasks.json: tarea para levantar Streamlit.
    • launch.json: depuración de un script Python y ejecución de Streamlit.
  • Archivo de ejemplo app.py listo para streamlit run.

  • Diagnóstico: comprueba que el intérprete activo apunta a .venv, que existan extensiones críticas y que streamlit esté instaldo.

  • Guía para “reset UI” en VSCode mediante Paleta de Comandos.

Funcionamiento

  1. Ejecuta el script desde la raíz del proyecto.
  2. Usa subprocess para invocar code --install-extension ....
  3. Crea .venv y usa su python interno para instalar streamlit.
  4. Escribe archivos en .vscode/.
  5. Muestra un resumen de verificación y pasos finales.

Casos de uso

  • Laboratorios donde los estudiantes rompen la UI o no tienen extensiones.
  • Migración desde Colab a VSCode + GitHub + Streamlit.
  • Preparación homogénea de máquinas en salas de cómputo.

2) Código (copiar y ejecutar como setup_vscode_streamlit.py)

import json
import os
import platform
import shutil
import subprocess
import sys
from pathlib import Path

# -----------------------------
# Utilidades de consola
# -----------------------------
def run(cmd, check=False):
    try:
        return subprocess.run(cmd, check=check, capture_output=True, text=True)
    except Exception as e:
        return subprocess.CompletedProcess(cmd, 1, "", str(e))

def info(msg): print(f"[INFO] {msg}")
def warn(msg): print(f"[WARN] {msg}")
def err(msg):  print(f"[ERROR] {msg}")

# -----------------------------
# Detección de plataforma y rutas
# -----------------------------
ROOT = Path.cwd()  # raíz del proyecto
VENV_DIR = ROOT / ".venv"
IS_WINDOWS = platform.system() == "Windows"

# Ruta al python del venv
PYTHON_VENV = VENV_DIR / ("Scripts/python.exe" if IS_WINDOWS else "bin/python")
PIP_VENV    = VENV_DIR / ("Scripts/pip.exe"    if IS_WINDOWS else "bin/pip")

# -----------------------------
# 1) Verificar que VSCode CLI esté disponible
# -----------------------------
def find_code_cli():
    # VSCode CLI suele ser "code"
    code_cmd = "code.cmd" if IS_WINDOWS else "code"
    path = shutil.which(code_cmd) or shutil.which("code")
    return path

CODE = find_code_cli()
if not CODE:
    warn("No se encontró la CLI de VSCode ('code').")
    warn("Instale VSCode y habilite 'code' en PATH. En VSCode: Command Palette → 'Shell Command: Install 'code' command in PATH' (macOS).")
else:
    info(f"CLI de VSCode detectada: {CODE}")

# -----------------------------
# 2) Instalar extensiones necesarias
# -----------------------------
EXTS = [
    "ms-python.python",
    "ms-python.vscode-pylance",
    "ms-toolsai.jupyter",
    "ms-toolsai.jupyter-keymap",
    "ms-toolsai.jupyter-renderers",
    "github.vscode-pull-request-github",
    "GitHub.copilot",
    "GitHub.copilot-chat"
]

def install_extensions():
    if not CODE:
        return
    info("Instalando extensiones de VSCode requeridas...")
    for ext in EXTS:
        r = run([CODE, "--install-extension", ext, "--force"])
        if r.returncode == 0:
            info(f"OK {ext}")
        else:
            warn(f"No se pudo instalar {ext}: {r.stderr.strip()}")

# -----------------------------
# 3) Crear entorno virtual y dependencias base
# -----------------------------
def ensure_venv_and_deps():
    if not VENV_DIR.exists():
        info("Creando entorno virtual .venv ...")
        r = run([sys.executable, "-m", "venv", ".venv"])
        if r.returncode != 0:
            err("Fallo creando .venv.")
            err(r.stderr)
            sys.exit(1)
    else:
        info("Entorno virtual .venv ya existe.")

    # Instalar streamlit en el venv
    if not PIP_VENV.exists():
        err("Pip en el venv no encontrado. El .venv parece estar corrupto.")
        sys.exit(1)
    info("Instalando 'streamlit' en el .venv ...")
    r = run([str(PIP_VENV), "install", "--upgrade", "pip"])
    if r.returncode != 0:
        warn("No se pudo actualizar pip (continuo).")

    r = run([str(PIP_VENV), "install", "streamlit"])
    if r.returncode == 0:
        info("OK streamlit instalado.")
    else:
        err("Fallo instalando streamlit.")
        err(r.stderr)

# -----------------------------
# 4) Generar carpeta .vscode y archivos de configuración
# -----------------------------
VSCODE_DIR = ROOT / ".vscode"
SETTINGS = VSCODE_DIR / "settings.json"
EXT_REC  = VSCODE_DIR / "extensions.json"
TASKS    = VSCODE_DIR / "tasks.json"
LAUNCH   = VSCODE_DIR / "launch.json"

def write_vscode_files():
    VSCODE_DIR.mkdir(exist_ok=True)

    # settings.json
    python_path = str(PYTHON_VENV)
    settings = {
        "python.defaultInterpreterPath": python_path.replace("\\", "\\\\") if IS_WINDOWS else python_path,
        "python.terminal.activateEnvironment": True,
        "python.analysis.autoImportCompletions": True,
        "python.analysis.typeCheckingMode": "basic",
        # Terminal por defecto en Windows: PowerShell
        **({"terminal.integrated.defaultProfile.windows": "PowerShell"} if IS_WINDOWS else {})
    }
    SETTINGS.write_text(json.dumps(settings, indent=4), encoding="utf-8")
    info("Escrito .vscode/settings.json")

    # extensions.json (recomendaciones)
    ext_rec = {
        "recommendations": EXTS
    }
    EXT_REC.write_text(json.dumps(ext_rec, indent=4), encoding="utf-8")
    info("Escrito .vscode/extensions.json")

    # tasks.json: tarea para ejecutar streamlit run app.py
    tasks = {
        "version": "2.0.0",
        "tasks": [
            {
                "label": "Run Streamlit",
                "type": "shell",
                "command": f"\"{python_path}\" -m streamlit run app.py" if IS_WINDOWS else f"{python_path} -m streamlit run app.py",
                "problemMatcher": [],
                "group": "build",
                "options": {
                    "cwd": "${workspaceFolder}"
                }
            }
        ]
    }
    TASKS.write_text(json.dumps(tasks, indent=4), encoding="utf-8")
    info("Escrito .vscode/tasks.json")

    # launch.json: depurar script Python y lanzar Streamlit
    launch = {
        "version": "0.2.0",
        "configurations": [
            {
                "name": "Python: Current File",
                "type": "python",
                "request": "launch",
                "program": "${file}",
                "console": "integratedTerminal"
            },
            {
                "name": "Streamlit: app.py",
                "type": "python",
                "request": "launch",
                "module": "streamlit",
                "args": ["run", "app.py"],
                "console": "integratedTerminal"
            }
        ]
    }
    LAUNCH.write_text(json.dumps(launch, indent=4), encoding="utf-8")
    info("Escrito .vscode/launch.json")

# -----------------------------
# 5) App de ejemplo para Streamlit
# -----------------------------
APP = ROOT / "app.py"
def write_sample_app():
    if APP.exists():
        info("app.py ya existe. No se sobrescribe.")
        return
    APP.write_text(
        """import streamlit as st

st.set_page_config(page_title="Demo Streamlit", page_icon="🐍", layout="centered")
st.title("Demo Streamlit para Programación con Python")
st.write("Esta es una app de ejemplo. Modifique este archivo y guarde para recargar.")

with st.sidebar:
    st.header("Parámetros")
    nombre = st.text_input("Nombre", "Estudiante")
    x = st.slider("x", 0, 100, 42)

st.success(f"Hola, {nombre}. x^2 = {x**2}")
""",
        encoding="utf-8"
    )
    info("Escrito app.py de ejemplo.")

# -----------------------------
# 6) Diagnóstico rápido
# -----------------------------
def diagnose():
    info("Diagnóstico rápido:")
    # a) VSCode CLI
    print(" - VSCode CLI:", "OK" if CODE else "NO DETECTADA")

    # b) Extensiones clave
    missing = []
    if CODE:
        r = run([CODE, "--list-extensions"])
        if r.returncode == 0:
            installed = set(e.strip().lower() for e in r.stdout.splitlines())
            for ext in EXTS:
                if ext.lower() not in installed:
                    missing.append(ext)
        else:
            warn("No pude listar extensiones de VSCode.")
    if missing:
        warn(f"Extensiones faltantes: {', '.join(missing)}")
    else:
        print(" - Extensiones clave: OK")

    # c) Intérprete en settings.json
    try:
        s = json.loads(SETTINGS.read_text(encoding="utf-8"))
        interp = s.get("python.defaultInterpreterPath", "")
        if interp and Path(interp).exists():
            print(" - Intérprete VSCode:", "OK (apunta a .venv)")
        else:
            warn("Intérprete en settings.json no existe o no apunta a .venv.")
    except Exception:
        warn("No se pudo leer .vscode/settings.json")

    # d) Streamlit instalado
    r = run([str(PYTHON_VENV), "-c", "import streamlit; print(streamlit.__version__)"])
    if r.returncode == 0:
        print(f" - Streamlit: OK ({r.stdout.strip()})")
    else:
        warn("Streamlit no está disponible en el .venv.")

# -----------------------------
# 7) Ejecución
# -----------------------------
def main():
    info(f"Proyecto: {ROOT}")
    install_extensions()
    ensure_venv_and_deps()
    write_vscode_files()
    write_sample_app()
    diagnose()

    print("\nPasos finales sugeridos:")
    print(" 1) Abra este proyecto en VSCode.")
    print(" 2) Presione Ctrl+Shift+P → 'Python: Select Interpreter' y elija el de .venv si fuera necesario.")
    print(" 3) Terminal nueva en VSCode debe abrir con '(.venv)'.")
    print(" 4) Ejecute la tarea 'Run Streamlit' (Terminal → Run Task) o 'F5' con 'Streamlit: app.py'.")
    print("\nReset de interfaz de VSCode (si la apariencia está alterada):")
    print(" - Ctrl+Shift+P → 'View: Reset View Locations'")
    print(" - Ctrl+Shift+P → 'View: Reset Layout'")
    print(" - Ctrl+Shift+P → 'Developer: Reload Window'")

if __name__ == "__main__":
    main()

3) Uso recomendado en clase

  1. Colocar setup_vscode_streamlit.py en la raíz del repo de cada equipo.

  2. Ejecutar:

    python setup_vscode_streamlit.py
  3. Abrir el folder en VSCode. Confirmar intérprete .venv.

  4. Correr Run Streamlit desde Tasks o F5 con la configuración “Streamlit: app.py”.

  5. Si la interfaz está desordenada:

    • Ctrl+Shift+PView: Reset View Locations
    • Ctrl+Shift+PView: Reset Layout
    • Ctrl+Shift+PDeveloper: Reload Window

4) Notas operativas

  • Windows PowerShell: si no activa .venv por política de ejecución, abrir Windows PowerShell como administrador y aplicar una vez:

    Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
  • Git: este script no toca credenciales ni git. Sugerir a estudiantes confirmar git --version y user.name/email.

  • Copilot: requiere que los estudiantes inicien sesión con su cuenta de GitHub dentro de VSCode y acepten permisos de Copilot.

  • Salas de cómputo: si code no está en PATH, abrir VSCode manualmente y ejecutar el comando de instalar code en PATH desde la Paleta.