El proyecto integra pertenencia, operaciones, relaciones, cardinalidad, producto cartesiano y funciones para construir un recomendador simple con JavaScript.
El objetivo es construir un recomendador simple de cursos a partir de intereses del usuario, temas disponibles y relaciones entre cursos y etiquetas.
El proyecto usa teoría de conjuntos para filtrar, comparar, combinar y ordenar información.
Una plataforma tiene cursos con distintas etiquetas. Un usuario indica sus intereses y el sistema debe recomendar cursos relacionados.
La recomendación se basará en intersección, unión, diferencia y cardinalidad.
El proyecto reúne varios conceptos vistos durante el curso.
Cada curso tendrá un identificador, un título y un conjunto de etiquetas.
const cursos = [
{
id: 1,
titulo: "Lógica para programación",
etiquetas: new Set(["lógica", "programación", "matemática"])
},
{
id: 2,
titulo: "Bases de datos",
etiquetas: new Set(["datos", "SQL", "programación"])
},
{
id: 3,
titulo: "Introducción a grafos",
etiquetas: new Set(["grafos", "relaciones", "matemática"])
}
];
console.log(cursos);
Los intereses del usuario se representarán como un conjunto.
const interesesUsuario = new Set(["programación", "datos", "lógica"]);
console.log(interesesUsuario);
Usar Set evita intereses repetidos y permite consultas de pertenencia.
Primero definimos operaciones reutilizables.
function union(a, b) {
return new Set([...a, ...b]);
}
function interseccion(a, b) {
return new Set([...a].filter(x => b.has(x)));
}
function diferencia(a, b) {
return new Set([...a].filter(x => !b.has(x)));
}
const A = new Set([1, 2, 3]);
const B = new Set([3, 4, 5]);
console.log(union(A, B));
console.log(interseccion(A, B));
console.log(diferencia(A, B));
Estas funciones serán la base del recomendador.
Las coincidencias se obtienen con la intersección entre intereses y etiquetas del curso.
const cursos = [
{
id: 1,
titulo: "Lógica para programación",
etiquetas: new Set(["lógica", "programación", "matemática"])
}
];
const interesesUsuario = new Set(["programación", "datos", "lógica"]);
function interseccion(a, b) {
return new Set([...a].filter(x => b.has(x)));
}
function coincidencias(usuario, curso) {
return interseccion(usuario, curso.etiquetas);
}
console.log(coincidencias(interesesUsuario, cursos[0]));
Cuantos más elementos tenga la intersección, mayor será la afinidad básica.
Un primer puntaje puede ser la cardinalidad del conjunto de coincidencias.
const cursos = [
{
id: 1,
titulo: "Lógica para programación",
etiquetas: new Set(["lógica", "programación", "matemática"])
}
];
const interesesUsuario = new Set(["programación", "datos", "lógica"]);
function interseccion(a, b) {
return new Set([...a].filter(x => b.has(x)));
}
function coincidencias(usuario, curso) {
return interseccion(usuario, curso.etiquetas);
}
function puntajeSimple(usuario, curso) {
return coincidencias(usuario, curso).size;
}
console.log(puntajeSimple(interesesUsuario, cursos[0])); // 2
El curso de lógica coincide con programación y lógica.
Para comparar mejor conjuntos de distinto tamaño, se puede usar la similitud de Jaccard.
function union(a, b) {
return new Set([...a, ...b]);
}
function interseccion(a, b) {
return new Set([...a].filter(x => b.has(x)));
}
function jaccard(a, b) {
const comun = interseccion(a, b);
const total = union(a, b);
return comun.size / total.size;
}
const usuario = new Set(["programación", "datos", "lógica"]);
const curso = new Set(["datos", "SQL", "programación"]);
console.log(jaccard(usuario, curso));
La afinidad entre usuario y curso se puede calcular aplicando Jaccard entre intereses y etiquetas.
const cursos = [
{
id: 2,
titulo: "Bases de datos",
etiquetas: new Set(["datos", "SQL", "programación"])
}
];
const interesesUsuario = new Set(["programación", "datos", "lógica"]);
function union(a, b) {
return new Set([...a, ...b]);
}
function interseccion(a, b) {
return new Set([...a].filter(x => b.has(x)));
}
function jaccard(a, b) {
const comun = interseccion(a, b);
const total = union(a, b);
return comun.size / total.size;
}
function afinidad(usuario, curso) {
return jaccard(usuario, curso.etiquetas);
}
console.log(afinidad(interesesUsuario, cursos[0]));
El resultado es un número entre 0 y 1.
Ahora se calcula un puntaje para cada curso y se ordena de mayor a menor.
const cursos = [
{
id: 1,
titulo: "Lógica para programación",
etiquetas: new Set(["lógica", "programación", "matemática"])
},
{
id: 2,
titulo: "Bases de datos",
etiquetas: new Set(["datos", "SQL", "programación"])
},
{
id: 3,
titulo: "Introducción a grafos",
etiquetas: new Set(["grafos", "relaciones", "matemática"])
}
];
const interesesUsuario = new Set(["programación", "datos", "lógica"]);
function union(a, b) {
return new Set([...a, ...b]);
}
function interseccion(a, b) {
return new Set([...a].filter(x => b.has(x)));
}
function coincidencias(usuario, curso) {
return interseccion(usuario, curso.etiquetas);
}
function jaccard(a, b) {
const comun = interseccion(a, b);
const total = union(a, b);
return comun.size / total.size;
}
function afinidad(usuario, curso) {
return jaccard(usuario, curso.etiquetas);
}
function recomendar(usuario, cursos) {
return cursos
.map(curso => ({
...curso,
puntaje: afinidad(usuario, curso),
coincidencias: [...coincidencias(usuario, curso)]
}))
.filter(curso => curso.puntaje > 0)
.sort((a, b) => b.puntaje - a.puntaje);
}
console.log(recomendar(interesesUsuario, cursos));
La lista final contiene solo cursos con alguna coincidencia.
La diferencia permite saber qué intereses del usuario no aparecen en las etiquetas de un curso.
const cursos = [
{
id: 3,
titulo: "Introducción a grafos",
etiquetas: new Set(["grafos", "relaciones", "matemática"])
}
];
const interesesUsuario = new Set(["programación", "datos", "lógica"]);
function diferencia(a, b) {
return new Set([...a].filter(x => !b.has(x)));
}
function interesesNoCubiertos(usuario, curso) {
return diferencia(usuario, curso.etiquetas);
}
console.log(interesesNoCubiertos(interesesUsuario, cursos[0]));
Esto ayuda a explicar por qué un curso recibió menor puntaje.
También se puede representar la información como una relación entre cursos y etiquetas.
const cursos = [
{
id: 1,
titulo: "Lógica para programación",
etiquetas: new Set(["lógica", "programación", "matemática"])
},
{
id: 2,
titulo: "Bases de datos",
etiquetas: new Set(["datos", "SQL", "programación"])
}
];
const relacionCursoEtiqueta = cursos.flatMap(curso =>
[...curso.etiquetas].map(etiqueta => [curso.id, etiqueta])
);
console.log(relacionCursoEtiqueta);
Cada par ordenado indica que un curso tiene una etiqueta.
El producto cartesiano entre usuarios y cursos permite evaluar todas las combinaciones posibles.
const cursos = [
{ id: 1, titulo: "Lógica para programación" },
{ id: 2, titulo: "Bases de datos" },
{ id: 3, titulo: "Introducción a grafos" }
];
function productoCartesiano(a, b) {
const resultado = [];
for (const x of a) {
for (const y of b) {
resultado.push([x, y]);
}
}
return resultado;
}
const usuarios = ["usuario1", "usuario2"];
console.log(productoCartesiano(usuarios, cursos.map(c => c.id)));
Un sistema más útil no solo recomienda, también explica el motivo.
const cursos = [
{
id: 1,
titulo: "Lógica para programación",
etiquetas: new Set(["lógica", "programación", "matemática"])
}
];
const interesesUsuario = new Set(["programación", "datos", "lógica"]);
function union(a, b) {
return new Set([...a, ...b]);
}
function interseccion(a, b) {
return new Set([...a].filter(x => b.has(x)));
}
function diferencia(a, b) {
return new Set([...a].filter(x => !b.has(x)));
}
function coincidencias(usuario, curso) {
return interseccion(usuario, curso.etiquetas);
}
function interesesNoCubiertos(usuario, curso) {
return diferencia(usuario, curso.etiquetas);
}
function jaccard(a, b) {
const comun = interseccion(a, b);
const total = union(a, b);
return comun.size / total.size;
}
function afinidad(usuario, curso) {
return jaccard(usuario, curso.etiquetas);
}
function explicar(usuario, curso) {
const comunes = coincidencias(usuario, curso);
const faltantes = interesesNoCubiertos(usuario, curso);
return {
curso: curso.titulo,
coincideEn: [...comunes],
noCubre: [...faltantes],
puntaje: afinidad(usuario, curso)
};
}
console.log(explicar(interesesUsuario, cursos[0]));
La explicación se construye con intersección y diferencia.
Un curso puede requerir que ciertos intereses estén presentes.
const cursos = [
{
id: 1,
titulo: "Lógica para programación",
etiquetas: new Set(["lógica", "programación", "matemática"])
},
{
id: 2,
titulo: "Bases de datos",
etiquetas: new Set(["datos", "SQL", "programación"])
},
{
id: 3,
titulo: "Introducción a grafos",
etiquetas: new Set(["grafos", "relaciones", "matemática"])
}
];
function esSubconjunto(a, b) {
return [...a].every(x => b.has(x));
}
const requisitos = new Set(["programación"]);
const compatibles = cursos.filter(curso =>
esSubconjunto(requisitos, curso.etiquetas)
);
console.log(compatibles);
La condición de compatibilidad se expresa como inclusión de conjuntos.
Antes de recomendar, conviene validar que cada curso tenga identificador, título y etiquetas.
const cursos = [
{
id: 1,
titulo: "Lógica para programación",
etiquetas: new Set(["lógica", "programación", "matemática"])
},
{
id: 2,
titulo: "Bases de datos",
etiquetas: new Set(["datos", "SQL", "programación"])
}
];
function cursoValido(curso) {
return Number.isInteger(curso.id) &&
typeof curso.titulo === "string" &&
curso.etiquetas instanceof Set;
}
console.log(cursos.every(cursoValido)); // true
Validar la estructura evita errores al operar con conjuntos.
El proyecto puede extenderse de varias maneras.
El flujo principal del proyecto queda organizado en pocas funciones.
const cursos = [
{
id: 1,
titulo: "Lógica para programación",
etiquetas: new Set(["lógica", "programación", "matemática"])
},
{
id: 2,
titulo: "Bases de datos",
etiquetas: new Set(["datos", "SQL", "programación"])
},
{
id: 3,
titulo: "Introducción a grafos",
etiquetas: new Set(["grafos", "relaciones", "matemática"])
}
];
const interesesUsuario = new Set(["programación", "datos", "lógica"]);
function union(a, b) {
return new Set([...a, ...b]);
}
function interseccion(a, b) {
return new Set([...a].filter(x => b.has(x)));
}
function coincidencias(usuario, curso) {
return interseccion(usuario, curso.etiquetas);
}
function jaccard(a, b) {
const comun = interseccion(a, b);
const total = union(a, b);
return comun.size / total.size;
}
function afinidad(usuario, curso) {
return jaccard(usuario, curso.etiquetas);
}
function recomendar(usuario, cursos) {
return cursos
.map(curso => ({
...curso,
puntaje: afinidad(usuario, curso),
coincidencias: [...coincidencias(usuario, curso)]
}))
.filter(curso => curso.puntaje > 0)
.sort((a, b) => b.puntaje - a.puntaje);
}
const recomendaciones = recomendar(interesesUsuario, cursos);
for (const curso of recomendaciones) {
console.log({
titulo: curso.titulo,
puntaje: curso.puntaje,
coincidencias: curso.coincidencias
});
}
El resultado final puede mostrarse en consola, en una página web o en una interfaz interactiva.
Este proyecto muestra cómo la teoría de conjuntos puede convertirse en una solución concreta. Las operaciones matemáticas permiten filtrar datos, comparar intereses, medir similitud, explicar recomendaciones y modelar relaciones.
La teoría de conjuntos no es solo una base abstracta: también es una herramienta práctica para programar sistemas claros, razonables y fáciles de extender.