|
| 1 | +# Reporte de Evaluación — Proyecto I (Programación, 1er año) |
| 2 | + |
| 3 | +- **Issue:** #250 |
| 4 | +- **Repositorio:** https://github.com/niitse34/CM-Chess-Club |
| 5 | +- **Estudiante:** Leonardo Córdova Rosas |
| 6 | +- **Grupo:** C122 |
| 7 | +- **Descripción del issue:** Planificador de eventos para un club de ajedrez, GUI en Streamlit, lógica de no solapamiento. |
| 8 | +- **Fecha de evaluación:** 2026-07-06 |
| 9 | + |
| 10 | +--- |
| 11 | + |
| 12 | +## Resumen de ejecución (lo que realmente corrí) |
| 13 | + |
| 14 | +Se creó un entorno aislado con `uv venv --python 3.12` y se instaló `streamlit` |
| 15 | +(1.59.0). El proyecto **no es una app de consola**: es una app web Streamlit |
| 16 | +(`gui.py` como punto de entrada, `run.sh` → `streamlit run gui.py`). Se ejecutó |
| 17 | +de tres formas: |
| 18 | + |
| 19 | +1. **`streamlit.testing.v1.AppTest`** recorriendo las 6 páginas del menú |
| 20 | + (`Events`, `Add Event`, `Find Slot`, `Resources`, `Save/Load`, `Settings`). |
| 21 | + **Todas renderizan sin excepción.** |
| 22 | +2. **Ejercicio directo de la lógica de dominio** (`main.ChessClub`) con 12 casos |
| 23 | + de prueba cubriendo validaciones, solapamiento, piezas de repuesto y |
| 24 | + persistencia. **Todos se comportaron correctamente** (detalle abajo). |
| 25 | +3. **Servidor Streamlit real** (`streamlit run gui.py --server.headless true`): |
| 26 | + arrancó limpio y respondió **HTTP 200** en la raíz. |
| 27 | + |
| 28 | +Resultado de los 12 casos de lógica (todos correctos): |
| 29 | + |
| 30 | +| # | Caso | Resultado observado | |
| 31 | +|---|------|---------------------| |
| 32 | +| 1 | friendly_match válido (board + pieces) | `(True, 'Scheduled: Amistosa')` | |
| 33 | +| 2 | friendly_match sin piezas | `(False, 'Missing required resources ...')` | |
| 34 | +| 3 | reloj en una clase (exclusión) | `(False, 'Clocks only for tournaments and team matches')` | |
| 35 | +| 4 | `end <= start` | `(False, 'Invalid time: end must be after start')` | |
| 36 | +| 5 | evento hoy (pasado) | `(False, 'Events can only be scheduled from tomorrow onwards')` | |
| 37 | +| 6 | evento en día bloqueado (Monday) | `(False, 'Events cannot be scheduled on Mondays')` | |
| 38 | +| 7 | clase de 30 min (< 1h mínimo) | `(False, 'class requires minimum 1h duration')` | |
| 39 | +| 8 | doble reserva del mismo recurso (solapamiento) | `(False, 'Unavailable or missing: Casual Board 1')` | |
| 40 | +| 9 | recurso inexistente | `(False, 'Unavailable or missing: nope_id (not found)')` | |
| 41 | +| 10 | `find_next_slot(1h, ['c_board_1'])` | devolvió `2026-07-07 09:00:00` | |
| 42 | +| 11 | pool de piezas: 6 eventos × 10 piezas > 50/día | los 5 primeros OK, el 6º `(False, 'Not enough spare pieces ... 50 available, 60 needed')` | |
| 43 | +| 12 | guardar + recargar JSON | 1 evento persistido y releído correctamente | |
| 44 | + |
| 45 | +**La lógica central de no-solapamiento y validación funciona de verdad al |
| 46 | +ejecutar, no solo en el papel.** |
| 47 | + |
| 48 | +--- |
| 49 | + |
| 50 | +## 1. Qué hace el programa |
| 51 | + |
| 52 | +Es un **planificador de eventos para un club de ajedrez ("Critical Mass Chess |
| 53 | +Club")** con interfaz web construida en Streamlit. El punto de entrada es |
| 54 | +`gui.py` (`gui.py:1-48`); se lanza con `bash run.sh` (`run.sh:2`) y abre en |
| 55 | +`http://localhost:8501`. El modelo de dominio vive en `main.py` (clase |
| 56 | +`ChessClub`, `main.py:8`), las entidades en `models.py` (`Resource`, `Event`) y |
| 57 | +la persistencia en `file_processing.py`. |
| 58 | + |
| 59 | +El flujo principal: el club gestiona **recursos** (salas, equipamiento —tableros, |
| 60 | +piezas, relojes, proyector— y personal —entrenadores FIDE, árbitros, |
| 61 | +comentaristas—, definidos en `resources.json`). El usuario agenda **eventos** de |
| 62 | +seis tipos (torneo, clase, enfrentamiento, partida amistosa, análisis, |
| 63 | +simultánea) desde la página "Add Event" (`gui.py:77-146`). Al agendar, el sistema |
| 64 | +valida en cascada: tiempo válido, fecha futura, día no bloqueado, duración |
| 65 | +mínima, horario del club, **disponibilidad (no solapamiento)** de cada recurso, |
| 66 | +**correquisitos y exclusiones** declarados en JSON, y el **pool diario de piezas |
| 67 | +de repuesto**. Además ofrece: búsqueda del próximo hueco libre |
| 68 | +(`find_next_slot`, `main.py:171`), monitoreo de carga horaria de entrenadores con |
| 69 | +sugerencia de alternativa (`get_coach_workload`/`suggest_alternative_coach`, |
| 70 | +`main.py:222-254`), y un panel "Settings" para editar tipos de evento, recursos y |
| 71 | +restricciones en caliente. |
| 72 | + |
| 73 | +Es un proyecto **notablemente ambicioso para 1er año**: el dominio está bien |
| 74 | +pensado y las reglas de negocio están externalizadas a configuración. |
| 75 | + |
| 76 | +## 2. Organización del código |
| 77 | + |
| 78 | +Muy buena para el nivel. El código está **repartido en cuatro módulos** con |
| 79 | +responsabilidades claras, en vez de un `main.py` monolítico: |
| 80 | + |
| 81 | +- `models.py` — entidades `Resource` y `Event` con `to_dict()` para serializar |
| 82 | + (`models.py:3-31`). |
| 83 | +- `main.py` — clase `ChessClub` con toda la lógica de dominio, bien dividida en |
| 84 | + métodos cortos y de nombre expresivo (`search_resource`, `check_available`, |
| 85 | + `validate_restrictions`, `schedule_event`, `find_next_slot`, `delete_event`…). |
| 86 | +- `file_processing.py` — funciones libres `read_json`/`write_json` y clase |
| 87 | + `FileProcessing` para guardar/cargar (`file_processing.py:5-52`). |
| 88 | +- `gui.py` — capa de presentación Streamlit, separada de la lógica. |
| 89 | + |
| 90 | +Los nombres de variables y funciones son claros y en inglés consistente. Hay |
| 91 | +reutilización real: `check_available` se llama tanto desde `schedule_event` |
| 92 | +como desde `find_next_slot`; `search_resource` centraliza la búsqueda. La |
| 93 | +separación presentación/lógica es exactamente lo que se espera y rara vez se ve |
| 94 | +tan limpio en un primer proyecto. `main.py:1` empieza con una línea en blanco |
| 95 | +(cosmético, sin efecto). |
| 96 | + |
| 97 | +## 3. Corrección funcional (basada en ejecución real) |
| 98 | + |
| 99 | +**Arranca perfectamente** y las 6 páginas renderizan sin `Traceback` (ver |
| 100 | +"Resumen de ejecución"). El servidor real devolvió HTTP 200. |
| 101 | + |
| 102 | +La lógica hace **exactamente** lo que promete el issue y el informe. Verificado |
| 103 | +al correr (`main.py:85-169` para `schedule_event`): |
| 104 | + |
| 105 | +- **No solapamiento** (`check_available`, `main.py:25-34`): la condición |
| 106 | + `not (end <= event.start or start >= event.end)` es el algoritmo correcto de |
| 107 | + intersección de intervalos. El caso 8 confirmó que rechaza doble reserva del |
| 108 | + mismo tablero. |
| 109 | +- **Correquisitos y exclusiones** (`validate_restrictions`, `main.py:59-83`): |
| 110 | + casos 2 y 3 confirmaron el rechazo con mensajes descriptivos. |
| 111 | +- **Validaciones de tiempo/fecha/duración/día bloqueado**: casos 4-7 todos |
| 112 | + correctos. |
| 113 | +- **Pool de piezas de repuesto** (`main.py:151-161`): caso 11 confirmó el |
| 114 | + rechazo del 6º evento al exceder 50 piezas/día. |
| 115 | +- **Persistencia** (caso 12): round-trip de guardado/carga correcto. |
| 116 | + |
| 117 | +Validación de entradas: **sólida**. Se manejan recursos inexistentes (caso 9), |
| 118 | +formatos de hora mal en config con `try/except` (`main.py:127-128`), y valores |
| 119 | +de política no numéricos (`main.py:50-57`). La GUI también valida campos vacíos |
| 120 | +antes de agendar (`gui.py:129-134`). |
| 121 | + |
| 122 | +**Discrepancia menor informe↔config:** el informe (`report.md:51`) dice "los |
| 123 | +relojes solo pueden ser utilizados en torneos", pero la regla real en |
| 124 | +`resources.json:145-147` los permite en `tournament` **y** `team_match`. El |
| 125 | +mensaje de error del código ("Clocks only for tournaments and team matches") es |
| 126 | +el correcto; el informe simplificó de más en ese punto. |
| 127 | + |
| 128 | +No encontré ninguna opción que lanzara excepción durante el recorrido. |
| 129 | + |
| 130 | +## 4. Buenas prácticas de Python (nivel principiante) |
| 131 | + |
| 132 | +Muy por encima del nivel esperado: |
| 133 | + |
| 134 | +- **Legibilidad e indentación**: consistentes en todo el proyecto. |
| 135 | +- **`try/except` donde toca**: parseo de config (`main.py:50-57`, `116-128`), |
| 136 | + guardas defensivas alrededor del pool de piezas (`main.py:152-161`). El |
| 137 | + `except Exception: pass` de `main.py:160` es demasiado amplio (silencia |
| 138 | + cualquier error), pero es un desliz menor. |
| 139 | +- **f-strings** usadas idiomáticamente en mensajes y en la GUI. |
| 140 | +- **Type hints ligeros** en varias firmas (`main.py:18,25,85,214,222`) — no se |
| 141 | + exigen en 1er año, es un plus. |
| 142 | +- **Comprehensions claras** (`main.py:64,154,157`) sin abusar de anidamiento. |
| 143 | +- Sin variables globales problemáticas: el estado vive en la instancia |
| 144 | + `ChessClub` y en `st.session_state`. |
| 145 | + |
| 146 | +Puntos a pulir: el `except Exception: pass` mencionado; y `type` se usa como |
| 147 | +nombre de parámetro/variable en varios sitios (`main.py:47,85,106…`), lo que |
| 148 | +sombrea la función incorporada `type()` — funciona, pero conviene evitarlo. |
| 149 | + |
| 150 | +## 5. Datos y persistencia |
| 151 | + |
| 152 | +Bien resuelto. **Dos archivos con responsabilidades separadas**: |
| 153 | +`resources.json` (configuración: recursos, tipos de evento, restricciones, |
| 154 | +`config`) y `CM_chess_club.json` (eventos agendados). Las estructuras son |
| 155 | +razonables: listas de objetos en memoria, serializadas vía `Event.to_dict()` |
| 156 | +(`models.py:22-31`) que guarda solo los **IDs** de los recursos y los rehidrata |
| 157 | +al cargar buscándolos por ID (`file_processing.py:48-51`) — decisión correcta |
| 158 | +que evita duplicar objetos. Las rutas se construyen con |
| 159 | +`os.path.dirname(os.path.abspath(__file__))` (`file_processing.py:6`), así que |
| 160 | +la app funciona sin importar el directorio de trabajo. El caso 12 confirmó el |
| 161 | +round-trip. Guardado automático tras cada alta/baja en la GUI |
| 162 | +(`gui.py:73,141`). |
| 163 | + |
| 164 | +## 6. Informe (`report.md`) |
| 165 | + |
| 166 | +El informe es **excelente y honesto**: describe con precisión lo que el código |
| 167 | +hace, explica las decisiones de diseño (por qué Streamlit, por qué JSON, por qué |
| 168 | +externalizar reglas), documenta las funcionalidades una por una y hasta incluye |
| 169 | +una sección de "Lecciones asimiladas" madura (`report.md:114-116`). No infla: |
| 170 | +casi todo lo que afirma está respaldado por el código ejecutado. |
| 171 | + |
| 172 | +Dos matices: |
| 173 | + |
| 174 | +- **Sobre-simplificación** en `report.md:51` (relojes "solo en torneos" cuando |
| 175 | + también valen para enfrentamientos) — ver dimensión 3. |
| 176 | +- **Testing declarado pero no incluido**: el informe describe "pruebas |
| 177 | + sistemáticas" (`report.md:110-112`) y `pyproject.toml` declara pytest como |
| 178 | + dependencia opcional (`pyproject.toml:27-31`), pero `.gitignore` excluye |
| 179 | + `test_edge_cases.py` y `test_main.py`, así que **los tests no se subieron al |
| 180 | + repo**. No penaliza en 1er año (los tests no se exigen), pero conviene saber |
| 181 | + que el informe habla de pruebas que no están versionadas. La verificación |
| 182 | + dinámica que hice yo confirma que la lógica sí es correcta. |
| 183 | + |
| 184 | +También hay un desajuste menor entre la sección "Estructura" del informe |
| 185 | +(`report.md:86-94`, menciona `main.py` como "programa") y la realidad (el |
| 186 | +programa se lanza por `gui.py`); es un residuo de una versión anterior. |
| 187 | + |
| 188 | +--- |
| 189 | + |
| 190 | +## Valoración global (orientativa, sin nota) |
| 191 | + |
| 192 | +Trabajo **sobresaliente para un primer proyecto de 1er año**. La arquitectura |
| 193 | +modular, la separación presentación/lógica, la externalización de reglas a JSON |
| 194 | +y la corrección real de la lógica de no-solapamiento y validaciones —todas |
| 195 | +verificadas ejecutando— lo colocan claramente por encima del nivel esperado. La |
| 196 | +principal fortaleza es que **funciona de verdad y está bien organizado**; las |
| 197 | +áreas de mejora son cosméticas (un `except` demasiado amplio, sombrear `type`) y |
| 198 | +de honestidad documental (tests mencionados pero no subidos, y una |
| 199 | +simplificación de más en una regla). Nada de esto compromete la calidad del |
| 200 | +entregable. |
0 commit comments