48. Proyecto integrador de matemática discreta aplicada

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.

48.1 Propósito del proyecto

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.

48.2 Problema a resolver

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.

El sistema debe permitir:
crear tareas y dependencias;
detectar ciclos de dependencia;
mostrar un orden válido de ejecución;
controlar permisos por rol;
registrar cambios de estado válidos;
consultar y persistir los datos con integridad.

Este alcance es suficientemente pequeño para una implementación didáctica y suficientemente rico para aplicar estructuras discretas de forma justificada.

48.3 Requisitos funcionales mínimos

  • Crear, listar y actualizar tareas con identificador único.
  • Asignar etiquetas y responsables a una tarea.
  • Declarar que una tarea debe terminar antes que otra.
  • Rechazar dependencias que creen ciclos.
  • Obtener tareas disponibles para comenzar.
  • Obtener un orden de ejecución de todas las tareas cuando exista.
  • Permitir transiciones de estado autorizadas.
  • Controlar acciones mediante roles y permisos.

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.

48.4 Mapa de conceptos del curso

Necesidad del proyectoModelo discretoAplicación
Identificar tareasconjuntos y funcionesIDs únicos y mapas de datos
Etiquetas y responsablesconjuntos y relacionesasignaciones muchos a muchos
Prerrequisitosgrafo dirigidoarista tarea → tarea posterior
Secuencia de trabajoDAG y orden topológicoplan de ejecución válido
Estados de una tareaautómata finitotransiciones permitidas
Acceso de usuariosálgebra booleana y grafosreglas y herencia de roles
Persistenciarelaciones y clavesesquema de base de datos
Rendimientocomplejidadelecció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.

48.5 Modelo de datos

Comienza por definir entidades y relaciones. Una tarea tiene atributos propios; una dependencia y una asignación son relaciones entre entidades.

Tarea(id, título, estado, prioridad, fecha_límite).
Usuario(id, nombre).
Rol(id, nombre).

Asignación(tarea_id, usuario_id).
Dependencia(prerequisito_id, tarea_id).
UsuarioRol(usuario_id, rol_id).

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.

48.6 Integridad del esquema

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.

48.7 Grafo de dependencias

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.

Diseño → Implementación.
Implementación → Pruebas.
Pruebas → Publicación.

La arista siempre se lee en la misma dirección:
«A debe suceder antes que B».

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.

48.8 Detectar dependencias cíclicas

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.

48.9 Plan de ejecución

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.

Diseño, Implementación, Pruebas, Publicación

es un orden válido para la cadena anterior.

Si Documentación no depende de Implementación, puede aparecer en distintas posiciones válidas.

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.

48.10 Tareas disponibles

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.

48.11 Autómata de estados de tarea

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.

pendiente → en_progreso o bloqueada.
en_progreso → bloqueada, completada o pendiente.
bloqueada → pendiente.
completada y cancelada: estados finales, salvo que el dominio permita reapertura.

Una transición no listada debe rechazarse.

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.

48.12 Validar transiciones

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"));   // false

El 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.

48.13 Roles y permisos

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.

Administrador: crea, reasigna y cancela.
Responsable: inicia y completa sus tareas asignadas.
Observador: solo consulta.

Permitir completar = esResponsable ∧ estáAsignado ∧ estadoEsEnProgreso.

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.

48.14 Ciclos de calendario y aritmética modular

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.

díaActual - díaBase ≡ 0 (mod 7)
⟹ corresponde revisión semanal.

Los índices circulares también sirven para rotar responsables:
índice = turno mod cantidadDeResponsables.

Los calendarios reales requieren considerar zonas horarias y reglas de fecha. El módulo modela la periodicidad, no reemplaza una biblioteca de fechas adecuada.

48.15 Consultas útiles

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.

Selección: tareas con estado = pendiente.
Join: tareas con sus responsables.
Diferencia: tareas asignadas menos tareas completadas.
Recursión: todos los prerequisitos indirectos de una tarea.

Cada consulta debe definir qué ocurre con duplicados y valores ausentes.

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.

48.16 Complejidad y escalabilidad

Un proyecto pequeño debe reconocer qué operaciones crecerán con la cantidad de tareas y dependencias.

OperaciónEnfoqueCosto típico
Ver prerequisitos directoslista de adyacencia inversaO(grado de entrada)
Detectar ciclo al agregar una aristabúsqueda de caminoO(V + E)
Ordenar todas las tareasKahn o DFSO(V + E)
Comprobar permisos directosconjunto o mapaaproximadamente O(1)
Listar tareas de un usuarioíndice por responsabledepende 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.

48.17 Seguridad y privacidad

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.

Autenticar identidad antes de operar.
Autorizar cada acción sensible.
Usar consultas parametrizadas.
No registrar contraseñas ni secretos.
Proteger sesiones y aplicar mínimo privilegio.
Registrar cambios importantes con actor y momento.

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.

48.18 Plan de pruebas

Las pruebas deben cubrir propiedades, no solo pantallas. Cada concepto discreto sugiere casos límite específicos.

Grafo: agregar A→B y B→A debe rechazar el ciclo.
Topológico: toda dependencia debe aparecer en orden correcto.
Autómata: transición inválida debe fallar.
Conjuntos: una asignación duplicada no debe duplicar responsables.
Permisos: un observador no debe modificar una tarea.
Datos: una clave foránea inválida debe rechazarse.

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.

48.19 Entregables sugeridos

  • Documento breve con problema, alcance y decisiones de modelado.
  • Diagrama de entidades y relaciones de la base de datos.
  • Diagrama del grafo de dependencias y del autómata de estados.
  • Implementación de las operaciones principales y pruebas.
  • Ejemplos de datos que incluyan casos válidos, ciclos y permisos rechazados.
  • Informe de complejidad de los algoritmos utilizados.
  • Sección de seguridad, limitaciones y trabajo futuro.

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.

48.20 Criterios de evaluación

CriterioQué se espera
Modeladoentidades, relaciones y direcciones justificadas
Correcciónciclos, transiciones y permisos tratados correctamente
Algoritmoselección adecuada y complejidad explicada
Datosclaves, restricciones y normalización razonable
Pruebascasos normales, bordes y fallos esperados
Seguridadautorización, validación y manejo responsable de datos
Comunicacióndocumentació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.

48.21 Extensiones opcionales

  • Agregar pesos de duración y calcular camino crítico.
  • Aplicar un algoritmo de asignación para equilibrar responsables.
  • Crear una visualización interactiva del DAG y resaltar ciclos.
  • Enviar recordatorios periódicos con reglas modulares.
  • Incorporar búsqueda por etiquetas y recomendaciones de responsables.
  • Registrar auditoría como secuencia verificable de eventos.
  • Analizar cuellos de botella con métricas de grafos.

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.

48.22 Errores frecuentes

  • Comenzar por la interfaz sin definir el modelo de entidades, estados y relaciones.
  • Usar dependencias con una dirección inconsistente.
  • Detectar ciclos solo en la interfaz y no en la lógica que persiste los datos.
  • Permitir transiciones de estado arbitrarias por no usar una tabla de reglas.
  • Guardar listas o relaciones complejas en campos que impiden consultar e imponer integridad.
  • Confundir autenticación con autorización o dejar permisos solo del lado cliente.
  • No probar casos inválidos, aislados o de crecimiento de datos.

48.23 Cierre del curso

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.

Preguntas para llevar a futuros proyectos:
¿qué objetos existen y qué relaciones los unen?
¿qué invariantes deben mantenerse?
¿qué estados y transiciones son válidos?
¿qué algoritmo responde a cada consulta?
¿cómo crece el costo?
¿qué datos y acciones necesitan protecció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.