Este proyecto final reúne los conceptos del curso en una aplicación pequeña pero completa. El objetivo no es acumular fórmulas, sino elegir modelos discretos correctos para representar datos, decisiones, dependencias, estados y restricciones.
Un proyecto integrador demuestra que la matemática discreta no es una colección de temas aislados. Cada concepto aporta una forma de modelar una parte del problema: los conjuntos agrupan, la lógica decide, los grafos conectan, los autómatas controlan estados y las relaciones organizan datos.
La propuesta de este tema es construir un planificador de tareas de equipo. Podrás adaptar el dominio a cursos, eventos, entregas, mantenimiento, desarrollo de software o cualquier proceso con tareas, dependencias y permisos.
Un equipo administra proyectos compuestos por tareas. Cada tarea puede tener responsables, etiquetas, fecha límite, estado y dependencias. Una tarea no debe comenzar hasta que sus prerequisitos estén completos.
Este alcance es suficientemente pequeño para una implementación didáctica y suficientemente rico para aplicar estructuras discretas de forma justificada.
Antes de programar, convierte cada requisito en una pregunta matemática: unicidad, conjunto, grafo dirigido, DAG, orden topológico, autómata finito o relación de autorización.
| Necesidad del proyecto | Modelo discreto | Aplicación |
|---|---|---|
| Identificar tareas | conjuntos y funciones | IDs únicos y mapas de datos |
| Etiquetas y responsables | conjuntos y relaciones | asignaciones muchos a muchos |
| Prerrequisitos | grafo dirigido | arista tarea → tarea posterior |
| Secuencia de trabajo | DAG y orden topológico | plan de ejecución válido |
| Estados de una tarea | autómata finito | transiciones permitidas |
| Acceso de usuarios | álgebra booleana y grafos | reglas y herencia de roles |
| Persistencia | relaciones y claves | esquema de base de datos |
| Rendimiento | complejidad | elección de algoritmos y estructuras |
Este mapa funciona como guía de diseño y como índice de la documentación final. Cada decisión técnica debe poder explicarse a partir de una necesidad del dominio.
Comienza por definir entidades y relaciones. Una tarea tiene atributos propios; una dependencia y una asignación son relaciones entre entidades.
Las relaciones intermedias permiten cardinalidad muchos a muchos. Por ejemplo, una tarea puede tener varios responsables y un usuario puede participar en varias tareas sin almacenar listas dentro de una única columna.
Las claves y restricciones hacen que las reglas del modelo se cumplan incluso si hay varios clientes o servicios que escriben en la base de datos.
CREATE TABLE Tarea (
id INTEGER PRIMARY KEY,
titulo TEXT NOT NULL,
estado TEXT NOT NULL,
prioridad INTEGER NOT NULL CHECK (prioridad BETWEEN 1 AND 5)
);
CREATE TABLE Dependencia (
prerequisito_id INTEGER NOT NULL REFERENCES Tarea(id),
tarea_id INTEGER NOT NULL REFERENCES Tarea(id),
PRIMARY KEY (prerequisito_id, tarea_id),
CHECK (prerequisito_id <> tarea_id)
);La restricción final evita una dependencia directa de una tarea consigo misma. Detectar ciclos de longitud mayor requiere lógica adicional en la aplicación o consultas recursivas, porque una clave foránea no conoce la estructura completa del grafo.
Representa cada tarea como un vértice. Usa una arista A → B cuando completar A es requisito para comenzar B. El resultado debe ser un DAG.
Si se agrega una arista que permite volver al origen por un camino existente, se crea un ciclo. En ese caso no hay orden topológico y el sistema debe rechazar la nueva dependencia o pedir al usuario que resuelva el conflicto.
Antes de agregar una dependencia A → B, se puede comprobar si B ya alcanza A. Si ocurre, la nueva arista cerraría un ciclo.
function hayCamino(adyacentes, origen, destino) {
const pendientes = [origen];
const visitados = new Set([origen]);
while (pendientes.length > 0) {
const actual = pendientes.pop();
if (actual === destino) return true;
for (const vecino of adyacentes.get(actual) ?? []) {
if (!visitados.has(vecino)) {
visitados.add(vecino);
pendientes.push(vecino);
}
}
}
return false;
}
function puedeAgregarDependencia(adyacentes, prerequisito, tarea) {
if (prerequisito === tarea) return false;
return !hayCamino(adyacentes, tarea, prerequisito);
}La comprobación busca el camino inverso antes de agregar la arista. En un sistema con muchas actualizaciones se pueden usar estrategias más avanzadas, pero este enfoque es claro y correcto para un proyecto inicial.
Una vez que el grafo es acíclico, el orden topológico entrega una secuencia donde cada prerequisito aparece antes que las tareas que dependen de él.
El orden topológico no asigna fechas ni personas. Solo resuelve la restricción parcial de precedencia. La planificación completa puede agregar duraciones, capacidades, prioridades y costos.
Una tarea está disponible si todos sus prerequisitos pertenecen al conjunto de tareas completadas y el usuario actual tiene permiso para actuar sobre ella.
function puedeIniciar(tarea, completadas, prerequisitos, tienePermiso) {
if (!tienePermiso) return false;
if (tarea.estado !== "pendiente") return false;
const requeridas = prerequisitos.get(tarea.id) ?? new Set();
return [...requeridas].every(id => completadas.has(id));
}Esta función combina conjuntos, cuantificación sobre prerequisitos y álgebra booleana. Cada condición expresa una regla separada y puede probarse de manera independiente.
Define estados explícitos para una tarea y transiciones válidas entre ellos. Un modelo simple puede usar pendiente, en_progreso, bloqueada, completada y cancelada.
El autómata impide combinaciones ambiguas, como completar una tarea cancelada sin una transición explícita. También simplifica la interfaz: cada estado puede mostrar solo las acciones permitidas.
const transiciones = new Map([
["pendiente", new Set(["en_progreso", "bloqueada", "cancelada"])],
["en_progreso", new Set(["pendiente", "bloqueada", "completada", "cancelada"])],
["bloqueada", new Set(["pendiente", "cancelada"])],
["completada", new Set()],
["cancelada", new Set()]
]);
function puedeCambiarEstado(actual, siguiente) {
return transiciones.get(actual)?.has(siguiente) ?? false;
}
console.log(puedeCambiarEstado("en_progreso", "completada")); // true
console.log(puedeCambiarEstado("completada", "pendiente")); // falseEl mapa representa la función de transición de un autómata finito. Las reglas de negocio pueden sumar validaciones, por ejemplo exigir evidencia de pruebas antes de permitir completada.
Define roles como conjuntos de permisos. Luego una regla booleana decide si una acción se autoriza según rol, propiedad de la tarea y estado.
El acceso no debe confiarse en que la interfaz oculte un botón. La misma autorización debe verificarse en el servidor antes de ejecutar una operación que modifique datos.
Si el sistema muestra revisiones periódicas, la aritmética modular ayuda a determinar cuándo corresponde cada una. Por ejemplo, una revisión cada 7 días usa el residuo de la diferencia de fechas respecto de una fecha base.
Los calendarios reales requieren considerar zonas horarias y reglas de fecha. El módulo modela la periodicidad, no reemplaza una biblioteca de fechas adecuada.
El proyecto puede responder preguntas con operaciones de conjuntos y relaciones: tareas pendientes de un usuario, proyectos con dependencias bloqueadas, responsables de una tarea o tareas alcanzables desde una fase.
Diseñar consultas desde el álgebra relacional ayuda a evitar resultados duplicados o joins accidentales. Los índices se agregan después de medir los patrones de acceso más frecuentes.
Un proyecto pequeño debe reconocer qué operaciones crecerán con la cantidad de tareas y dependencias.
| Operación | Enfoque | Costo típico |
|---|---|---|
| Ver prerequisitos directos | lista de adyacencia inversa | O(grado de entrada) |
| Detectar ciclo al agregar una arista | búsqueda de camino | O(V + E) |
| Ordenar todas las tareas | Kahn o DFS | O(V + E) |
| Comprobar permisos directos | conjunto o mapa | aproximadamente O(1) |
| Listar tareas de un usuario | índice por responsable | depende de sus asignaciones |
La complejidad no exige optimizar prematuramente. Sirve para identificar qué diseño seguirá siendo razonable cuando los datos ya no sean de ejemplo.
El proyecto debe tratar los datos de usuarios y tareas como activos. Define qué información es pública dentro del equipo, quién puede modificarla y qué acciones deben quedar registradas.
Si el proyecto implementa cuentas reales, usa mecanismos de autenticación y almacenamiento de credenciales provistos por plataformas o bibliotecas mantenidas. No conviertas los ejemplos criptográficos del curso en un sistema propio de producción.
Las pruebas deben cubrir propiedades, no solo pantallas. Cada concepto discreto sugiere casos límite específicos.
Agregar pruebas de propiedades aumenta la confianza: por ejemplo, comprobar automáticamente que un orden topológico respete todas las aristas, sin importar la secuencia concreta que el algoritmo devuelva.
Un proyecto integrador se evalúa por la coherencia entre modelo, implementación y pruebas. Una interfaz elaborada no compensa reglas imprecisas; una solución técnica correcta debe poder explicarse con claridad.
| Criterio | Qué se espera |
|---|---|
| Modelado | entidades, relaciones y direcciones justificadas |
| Corrección | ciclos, transiciones y permisos tratados correctamente |
| Algoritmos | elección adecuada y complejidad explicada |
| Datos | claves, restricciones y normalización razonable |
| Pruebas | casos normales, bordes y fallos esperados |
| Seguridad | autorización, validación y manejo responsable de datos |
| Comunicación | documentación clara de supuestos y límites |
Estos criterios pueden adaptarse a otro dominio. Lo importante es demostrar que cada herramienta matemática se usa para resolver una necesidad concreta, no como adorno conceptual.
Las extensiones solo deben agregarse después de que el núcleo sea correcto. Cada una introduce nuevas decisiones de datos, rendimiento y seguridad que también deben modelarse y probarse.
La matemática discreta permite pasar de una idea informal a un modelo verificable. Las demostraciones justifican propiedades; las recurrencias y la complejidad ayudan a medir algoritmos; los conjuntos, relaciones y grafos organizan datos; la lógica y los autómatas controlan decisiones y estados; la aritmética modular y la criptografía ayudan a proteger información.
Un buen programador no solo escribe código que funciona con un ejemplo: construye modelos claros, elige estructuras adecuadas, prueba límites y comunica los supuestos. Ese es el valor duradero de la matemática discreta aplicada.