Generador combinatorio de horarios universitarios sin cruces hecho en MS-DOS, recuperado después de ~30 años
Historia · Arquitectura · Formato de datos · Cómo compilar · Saneamiento de datos
Autor original: Oscar Zamora Plasencia · ZmS Software © 1995-1996 Sitio del autor: ozamora.com Estado: Archivo histórico preservado. No se modifica el código fuente.
Tercer año de Ingeniería de Sistemas en la Universidad de Lima, 1995.
Inscribirse a los cursos cada semestre era una experiencia frustrante:
- Llegabas al día de la matrícula con tu combinación ideal armada a mano, a lápiz, sobre una cuadrícula impresa.
- En la cola, una clase estaba llena o cerrada y todo el horario se caía.
- Había que rearmar combinaciones al vuelo, sin cruces, con la cola detrás y el reloj corriendo.
- Encontrar otra combinación válida en minutos, manualmente, era casi imposible. Más de una vez terminé matriculado en lo que sobraba, no en lo que quería.
La idea fue simple: hacer todas las permutaciones por adelantado. Que el programa genere todas las combinaciones válidas (sin cruces) antes de ir a la matrícula, para llegar con un plan A, B, C, D… y si una sección se cae, saltar a la siguiente combinación ya lista.
El "foco se prendió" mientras hacía guardias como veedor en la sala de cómputo de la facultad — entre turnos sin mucho que hacer, iterando ideas, terminé de armar el algoritmo iterativo que evita la recursión y detecta cruces con un bitmap 15×6 (horas × días).
En 1998 la universidad me pidió el código fuente. Decidí no entregarlo — quizás pensando en algún retorno financiero que nunca iba a llegar. Hoy, casi 30 años después, sé que probablemente no fue la decisión más acertada.
Por eso ahora lo publico como archivo histórico: fidedigno al original, con su Turbo Pascal y sus comentarios en español con CP850. Quizás el algoritmo de detección de cruces le sirva a alguien. Quizás no. Pero ya no se queda en un ZIP guardado en OneDrive.
El benchmark documentado verbatim en src/GDH.HLP reporta:
5 cursos · 84 secciones · 861 840 combinaciones evaluadas en 3 min 20 s sobre un 486DLC a 40 MHz con SMARTDRV → 125 030 horarios válidos generados · archivo temporal de 11.5 MB.
Las cifras dan:
- ~4 309 combinaciones evaluadas por segundo
- ~625 records válidos escritos a disco por segundo
Para un 486DLC de 1995 (≈6-8 MIPS escalar, disco mecánico con caché de software SMARTDRV), el programa está cerca del límite teórico de I/O: el caché absorbe los writes y la CPU casi no se aburre. El manual cita "alrededor de 1000 combinaciones/s" como cifra conservadora, pero el caso real medido es ~4× más rápido.
Las decisiones de diseño que lo hicieron eficiente (verificables en
procedure genera de src/HORA.PAS):
| Decisión | Por qué importa en 1995 |
|---|---|
| Enumeración iterativa (no recursiva) | El stack era de 64 KB; recursión profunda en 15 cursos hubiera reventado el modelo de memoria DOS |
Contador multi-base sobre top[1..totcur] |
Genera el producto cartesiano sin construir el árbol — memoria O(totcur) en vez de O(combinaciones) |
| Detección de cruce con bitmap 15×6 | Cada celda es O(1); detecta colisión apenas ocurre, no al final |
Early-exit en cruce (cruce := true) |
Apenas detecta una colisión deja de marcar el resto de horas |
Salida streaming a disco (file of regcuadre) |
125 030 horarios × 96 bytes = 12 MB → imposible en los 640 KB de memoria conventional de DOS |
Reutilizar vaciohor como baseline del bitmap |
Las "horas no deseadas" se aplican gratis, no como filtro post-hoc |
| Records compactos (96 bytes/horario, reducidos desde 176 en la Beta) | Reduce I/O y tamaño del archivo temporal en ~45% |
Hoy el mismo cálculo correría en milisegundos en cualquier laptop, pero la estructura del algoritmo sigue siendo la correcta: iterativo, bitmap de colisión, early-exit, streaming. Es básicamente lo que un solver moderno haría con las mismas restricciones de memoria embebida.
En febrero de 1996 (BETA96) agregué al proyecto la unidad src/MOUSE.PAS: un driver completo que habla con el ratón vía INT 33h del BIOS, con 10 cursores gráficos personalizados (flecha, reloj de arena, mano apuntando, check, etc.). El objetivo era claro: la siguiente versión del programa iba a soportar mouse — click sobre la grilla 15×6 para marcar horas, click directo sobre los ítems de las listas, botones presionables con el cursor.
Durante mucho tiempo creí que esa integración había quedado incompleta. El código fuente preservado en src/ (HORA11, 16-abr-1996) lo respaldaba:
HORA.PASdeclarauses crt, graph, uhora, uhora2;— sinmouse.- 11
readkeyy 47 códigos de tecla literales — todo por teclado. - El manual de HORA11 (21 tópicos) no menciona el ratón.
- La unidad
MOUSE.PASestaba presente desde feb-1996 pero sin enlazar.
Pero sí terminé de integrarla. La evidencia apareció en un backup
de OneDrive bajo _Archived_/DOS Applications/Horarios/: el
ejecutable compilado del 1 de junio de 1996 — 6 semanas posterior
al source preservado — junto con su manual de ayuda actualizado. La
evidencia decisiva está en el manual:
- ✅ Nuevo tópico "Usando el Mouse" abriendo la ayuda y declarando textualmente: "Esta versión soporta mouse, y se pueden desarrollar todas las tareas usando el mouse".
- ✅ 21 menciones de
mouse,clickobotóndescribiendo flujos específicos:- "click en un botón deseado del menú" (menú principal),
- "click en el ítem deseado, y luego en el botón OK" (
selectitem), - "click en las flechas de la ventana" (scrollbar de
selectitem), - "click en el recuadro deseado" / "click en el bloque deseado para
marcar o desmarcar la hora" (grilla 15×6 de
tabla), - "click en el botón Cancel" (diálogos).
- ✅ Manual ampliado: 24 tópicos vs. 21 del HORA11 anterior.
- ✅ Menú renombrado: "Adicionar Curso" → "Adicionar Sección" (más correcto).
- ⚠ Lo que NO es evidencia válida: una búsqueda ingenua del opcode
CD 33(INT 33h) en el.EXEarroja una sola coincidencia, en bytes que claramente son datos comprimidos del runtime de Turbo Pascal (no instrucción). Las llamadas INT 33h reales están dentro del segmento de código de la unitMOUSE.TPUenlazada, empaquetada por el compilador, y no son directamente detectables por byte search.
El código fuente de esa versión final nunca se conservó. Solo sobrevivió el EXE compilado, en release/1996-06-FINAL/ junto al manual y los drivers BGI necesarios para ejecutarlo en DOSBox.
Como el fuente real está perdido pero el comportamiento esperado está
documentado explícitamente en el manual de junio 1996, en
reconstructed/1996-06-FINAL-with-mouse/
está una re-implementación del propio autor (2026) que integra
MOUSE.PAS exactamente donde el manual dice que el mouse funcionaba:
menú principal, diálogos msgbox, listas selectitem (ítems, flechas
y track del scrollbar), formularios gettext, grilla 15×6 de tabla
y visor impcuadres.
No es decompilación del EXE: es una reconstrucción por fuente perdido sobre HORA11 (v1.1), con la misma API, el mismo estilo del autor y click adicional al teclado. Cubre punto a punto la especificación del manual. Detalle completo, supuestos y falsabilidad en reconstructed/1996-06-FINAL-with-mouse/RECONSTRUCTION-NOTES.md.
Ver también docs/codigo/MOUSE.PAS.md para el detalle de la unidad MOUSE.
Este repositorio preserva cuatro snapshots reales del proyecto a lo largo de su evolución, más una reconstrucción moderna del fuente perdido de junio 1996, más documentación para entenderlo sin necesidad de ejecutarlo en DOS.
| Carpeta | Contenido | Estatus |
|---|---|---|
| release/1996-06-FINAL/ | EXE final con mouse (1 jun 1996) | Binario real — fuente perdida |
| reconstructed/1996-06-FINAL-with-mouse/ | Reconstrucción del fuente con mouse | Re-implementación del autor (2026) según el manual |
| src/ | Versión HORA11 (16 abr 1996, v1.1 pre-final) | Último source preservado, sin mouse integrado |
| history/1995-08-HORABETA/ | v1.0 Beta — agosto 1995 | Primera versión conservada |
| history/1995-09-HORA10/ | v1.0 estable — septiembre 1995 | Incluye CAMBIOS.TXT original |
| history/1996-02-BETA96/ | Beta 96 — febrero 1996 | Introduce HORA.PAS y MOUSE.PAS |
| docs/ | Documentación moderna | Historia, arquitectura, formatos, cómo compilar |
- docs/HISTORIA.md — línea de tiempo 1995→1996 y cambios verificados entre snapshots
- docs/ARQUITECTURA.md — módulos, dependencias, estructuras de datos
- docs/FORMATO-DATOS.md — layout binario de
.DAT,.HORy formato de.HLP - docs/COMPILACION.md — cómo compilar/ejecutar hoy (DOSBox + TP7 o Free Pascal)
- docs/SANEAMIENTO.md — qué datos personales fueron eliminados o reemplazados y por qué
- docs/CAMBIOS-ORIGINALES.txt —
CAMBIOS.TXToriginal preservado en CP850 - docs/codigo/ — documentación externa por cada unidad Pascal
horarios/
├── release/
│ └── 1996-06-FINAL/ ⭐ EXE final con mouse (1 jun 1996) — fuente perdida
├── reconstructed/
│ └── 1996-06-FINAL-with-mouse/ 🔧 Re-implementación del fuente perdido (2026)
├── src/ ★ HORA11 — último source preservado (16 abr 1996)
├── history/
│ ├── 1995-08-HORABETA/ v1.0 Beta
│ ├── 1995-09-HORA10/ v1.0 estable
│ └── 1996-02-BETA96/ Beta 96
├── docs/
│ ├── HISTORIA.md
│ ├── ARQUITECTURA.md
│ ├── FORMATO-DATOS.md
│ ├── COMPILACION.md
│ ├── SANEAMIENTO.md
│ ├── CAMBIOS-ORIGINALES.txt
│ └── codigo/ Docs por unidad: HORA, HORARIO, UHORA, UHORA2, MOUSE
├── scripts/sanitize-data.ps1
├── .vscode/ Asocia .PAS a Pascal con encoding CP850
├── .gitignore
└── .gitattributes
- No se editan los
.PASni.HLPoriginales. Toda la documentación es externa (docs/codigo/*.md). - Los binarios
.DATy.HORfueron SANEADOS — contenían mis horarios reales de cada semestre, con los apellidos reales de mis profesores y el plan de estudios identificable. Decidí exponer mi propia historia pero no la de terceros. Estructura, tamaño, formato de records y timestamps preservados; contenido string reemplazado por placeholders sintéticos. Ver docs/SANEAMIENTO.md. - Timestamps preservados (
LastWriteTimeoriginal de 1995-1996) en todas las copias. - Encoding original CP850 (DOS Latin español). VS Code lo reabre correctamente gracias a .vscode/settings.json.
- CRLF mantenido en todos los archivos de texto (proyecto DOS).
- Lenguaje: Turbo Pascal (modo gráfico VGA 640×480 vía unidad
graph/ BGI) - Capacidades: hasta 15 cursos · 25 secciones por curso · 375 secciones totales
- Algoritmo: enumeración combinatoria iterativa con bitmap de cruces (~4 300 combinaciones/s en un 486DLC a 40 MHz — ver sección de eficiencia)
- Salidas: visualización en pantalla + impresión a
LPT1(horario completo o lista de secciones) - Persistencia: typed files binarios (
file of <record>), formato propietario - Entrada: teclado completo (flechas, Tab, Enter, Esc, F1, F5) en el código preservado; el binario FINAL agregó mouse según el manual de junio 1996 (reconstrucción en reconstructed/1996-06-FINAL-with-mouse/)
Ver docs/ARQUITECTURA.md para el detalle completo.