
Herramienta de Gestión de Proyectos
Bienvenido a este capítulo donde estaremos desarrollando una herramienta de "Gestión de Proyectos" aplicando los conceptos de "lógica básica" y "comunicación básica" que hemos visto en este repositorio. Todas las herramientras usadas en este caso ya han sido explicadas en los capítulos correspondientes. De todas maneras, estaré dejando los enlaces según vea conveniente. Sin más dilación...
Empecemos...
...definiendo ¿qué es una herramienta de gestión de proyectos?. Y básicamente es una aplicación que te ayuda a organizar, planificar, ejecutar, etc., diferentes tareas y recursos que forman parte de un proyecto.
Y ¿para qué sirve una herramienta de este tipo? Pues, dependiendo de la complejidad de la misma, puede ayudarnos más o menos a: aumentar la productividad, mejorar la organización, facilitar la colaboración (si se crea para equipos), etc. Ahora hablemos...
...sobre el proyecto.
En mi caso, quería crear una herramienta de "gestión de proyectos" que me sirviera para trabajar en mi PC de manera personal, que no se conectase a internet y con un aspecto bastante simple y sobrio. Esto, porque he probado muchas herramientas y no me terminan de convencer. Así que pensé que se podía crear una.
A continuación, deberás escoger tu IA de preferencia. Yo trabajo con la IA de Google AI Studio, por razones que ya expliqué aquí. Pero la lógica y comunicación se aplica a cualquier IA de tu preferencia.

Lógica
Para definir la lógica, empezaremos definiendo las características principales que queremos que tenga la herramienta. Preguntas como: ¿qué quiero que haga? ¿cuando haga clic aquí qué va a pasar? ¿cómo va a interactuar tal o cual cosa? ¿cómo se va a ver? ¿qué atributos quiero ver yo en esta herramienta? entre otras más, nos ayudarán a definir la lógica.
Recordemos que la lógica la he dividido en dos partes: por un lado está la parte de Fondo (estructura), que nos ayuda definiendo las funciones, y por otra parte está la Forma (estética), que nos ayuda definiendo la parte visual.
Con esto como punto de partida, definí que habían tres actividades clave que quería que la herramienta me permitiera hacer: agregar un proyecto, agregar tareas a cada proyecto y guardar mi progreso por si quedara algún pendiente para el día siguiente. Te dejo un esquema de mi lógica en base al "Fondo (estructura)":

Como verás, agregué un punto que dice "Lenguaje HTML". Esto debido a que, como lo iba a mostrar en mi web y esta es "Wordpress", solo me deja incluir un código HTML, como te lo mostré en la guía de Google Sites. Pero, si tú vas a generarlo de cero y no te interesa mostrarlo, te recomiendo que lo hagas generando códigos separados para que tengas más control.
A partir de definir las tres actividades principales, empezamos a completar las funcionalidades de cada una como mostró la imagen de arriba. Aquí te dejo un desglose de funcionalidades y componenetes que tuve en cuenta:
Con la estructura clara pasaremos a definir la lógica visual. En mi caso, como quería que tuviera un aspecto simple y sobrio, me decanté por un estilo minimalista. Un aspecto importante que te permite definir la lógica de forma (estética) es el formato.
El formato tiene mucho que ver porque es el que va a dar forma a tu herramienta. Usualmente manejo dos tipos: contenedores y tablas. En este caso, como es una herramienta que divide varios "proyectos", me conviene más verlo en forma de contenedores y no de tablas.
Por lo demás, es definir de manera básica el aspecto en atributos como los colores, forma de botones, opciones de menú y uno muy importante es la "adaptabilidad". Básicamente la "adaptabilidad" hace referencia a que nuestra herramienta se pueda ver bien en PC si acortamos o agrandamos la página, y en móviles.
Nuestra lógica de "Forma" quedaría de la siguiente manera:

Simple, ¿verdad?. Yo sé que puede parecer complejo al inicio tener que hacer todo esto, pero te aseguro que es la manera en cómo te puedes ahorrar bastantes pasos y dolores de cabeza después. Ahora, ¿qué toca?.
Definir la comunicación...
...que no es otra cosa que hacer el prompt. Pero como ya hemos visto, de esta depende que la IA te entienda, te entienda a medias o no te entienda. Yo he propuesto, para este ejercicio, definir una jerarquía lineal haciendo una descripción de la herramienta y su comportamiento viéndolo de arriba hacia abajo.
Recuerda que la idea detrás de estructurar un buen prompt es evitar la máxima cantidad de errores y llegar al resultado de la manera más rápida.
Entonces, el promtp, en mi caso, quedaría de la siguiente manera:
Ahora usaré un "Codepen" donde he pegado el código "HTML" que me generó Google AI Studio. Recuerda que para verlo y probarlo puedes dar clic donde dice "Run Pen". Además, en la herramienta "Codepen" encontrarás un botón que dice "HTML" donde podrás clicar para ver el código que nos ha generado:
Como podrás observar, la herramienta está completamente funcional y era lo que buscábamos, que en pocos pasos, con menos pruebas y errores, lleguemos al resultado que queríamos. Aun así, nos faltaría agregar la lógica de "Forma (estética)" que habíamos definido anteriormente.
Te dejo abajo el promtp con las indicaciones que le hice a la IA:
Ahora podrás ver la herramienta terminada en el siguiente "Codepen":
Con esto ya podríamos dar por terminado el proyecto. Como verás, lo más complicado ha sido definir la lógica y la comunicación. Pero como has podido observar, esto nos ayudó a reducir el número de intentos y errores notablemente.
De todas formas, como ya te había comentado, te recomiendo trabajar el código por separado. Esto te dará más control y flexibilidad al momento de querer hacer cambios o escalar tu idea inicial.
Al final de tejo el código para que lo copies y pegues en la herramienta que uses para prototipar. Antes de terminar quiero que tengas en cuenta una cosa...
...y es que este contenido es educativo y no se permite la reproducción con fines lucrativos. Todo el material aquí expuesto se ha creado para ser compartido con las personas que se interesen en conocer y profundizar sobre el manejo de IA Generativa.
¡IA nos vemos pronto!
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Para responsive -->
<title>Gestor de Proyectos Personal</title>
<style>
body {
font-family: sans-serif;
background-color: #f4f4f4;
color: #333;
}
.container {
background-color: #fff;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
h2 {
text-align: center;
}
input, select, textarea {
width: 100%;
padding: 10px;
margin-bottom: 10px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
button {
background-color: #1a1a1a;
color: #fff;
padding: 8px 12px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.9em;
}
.project {
border: 1px solid #ddd;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 20px;
padding: 15px;
border-radius: 8px;
}
.progress-bar {
background-color: #ddd;
border-radius: 4px;
height: 20px;
margin-bottom: 10px;
}
.progress {
height: 100%;
border-radius: 4px;
width: 0%;
}
.task-list {
border: 1px solid #ddd;
margin-bottom: 10px;
padding: 10px;
list-style: none;
}
.task-list li {
margin-bottom: 5px;
}
.select-wrapper {
position: relative;
display: inline-block;
width: 100%;
}
.select-wrapper select {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background: transparent url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='6' viewBox='0 0 12 6'%3E%3Cpath fill='%23666' d='M0.379,0.909l5.243,5.241L11.621,0.909L12,1.288L6,7.288L-5.34359375e-16,1.288L0.379,0.909z'/%3E%3C/svg%3E") no-repeat right 10px center;
padding-right: 30px;
}
details summary {
cursor: pointer;
margin-bottom: 10px;
}
.footer { /* Estilos para el pie de página */
text-align: center;
font-size: 0.8em;
color: #bbb;
margin-top: 20px;
}
/* Media query para responsive (ajusta a tus necesidades) */
@media (max-width: 768px) {
.container {
padding: 15px; /* reduce padding en pantallas pequeñas */
}
}
</style>
</head>
<body>
<div class="container">
<h2>Agregar Proyecto</h2>
<input type="text" id="projectName" placeholder="Nombre del proyecto" required>
<textarea id="projectDescription" placeholder="Descripción del proyecto" required></textarea>
<input type="date" id="startDate" required>
<input type="date" id="endDate" required>
<button onclick="addProject()">Agregar Proyecto</button>
</div>
<div class="container">
<h2>Agregar Tarea</h2>
<input type="text" id="taskName" placeholder="Nombre de la tarea" required>
<textarea id="taskDescription" placeholder="Descripción de la tarea" required></textarea>
<div class="select-wrapper"> <select id="projectSelection" disabled></select></div>
<button onclick="addTask()">Agregar Tarea</button>
</div>
<div id="projectsContainer" class="container">
<h2>Proyectos</h2>
</div>
<div class="container">
<button onclick="clearProjects()">Limpiar Proyectos</button>
<button onclick="saveProjects()">Guardar Progreso</button>
</div>
<div class="footer">
Diseñado por Tomas Dulanto
</div>
<script>
let projects = [];
function addProject() {
const projectName = document.getElementById('projectName').value.trim();
const projectDescription = document.getElementById('projectDescription').value.trim();
const startDate = document.getElementById('startDate').value;
const endDate = document.getElementById('endDate').value;
if (projectName === "" || projectDescription === "" || startDate === "" || endDate === "") {
alert("Por favor, complete todos los campos del proyecto.");
return;
}
const newProject = {
name: projectName,
description: projectDescription,
startDate: startDate,
endDate: endDate,
tasks: [],
completedTasks: 0
};
projects.push(newProject);
renderProjects();
updateProjectSelection();
document.getElementById('projectName').value = '';
document.getElementById('projectDescription').value = '';
document.getElementById('startDate').value = '';
document.getElementById('endDate').value = '';
}
function addTask() {
const taskName = document.getElementById('taskName').value.trim();
const taskDescription = document.getElementById('taskDescription').value.trim();
const selectedProjectIndex = document.getElementById('projectSelection').value;
if (taskName === "" || taskDescription === ""|| selectedProjectIndex === null ) {
alert("Por favor, complete todos los campos de la tarea y seleccione un proyecto.");
return;
}
const newTask = {
name: taskName,
description: taskDescription,
status: "Sin empezar"
};
projects[selectedProjectIndex].tasks.push(newTask);
renderProjects();
document.getElementById("taskName").value= "";
document.getElementById("taskDescription").value = "";
updateTaskCounter(selectedProjectIndex);
}
function updateTaskCounter(projectIndex) {
let completedCount = 0;
for (const task of projects[projectIndex].tasks){
if(task.status === "Finalizado"){
completedCount++;
}
}
projects[projectIndex].completedTasks = completedCount;
updateProgressBar(projectIndex);
}
function calculateProgress(projectIndex) {
if (!projects[projectIndex] || !projects[projectIndex].tasks ) return 0;
const totalTasks = projects[projectIndex].tasks.length;
if(totalTasks === 0 ) return 0;
return Math.round((projects[projectIndex].completedTasks / totalTasks) * 100);
}
function updateProgressBar(projectIndex) {
const progressBar = document.querySelector(`.project:nth-child(${projectIndex + 1}) .progress`);
if (progressBar) {
progressBar.style.width = `${calculateProgress(projectIndex)}%`;
}
}
function renderProjects() {
const projectsContainer = document.getElementById('projectsContainer');
projectsContainer.innerHTML = '<h2>Proyectos</h2>';
projects.forEach((project, index) => {
const projectDiv = document.createElement('div');
projectDiv.className = 'project';
let progress = calculateProgress(index);
let progressBarColor = progress === 0 ? "#ff1f00" : progress === 100 ? "#24d83d" : "#0070ff";
projectDiv.innerHTML = `<h3>${project.name}</h3>
<p>${project.description}</p>
<p>Inicio: ${project.startDate}, Fin: ${project.endDate}</p>
<div class="progress-bar">
<div class="progress" style="width: ${progress}%; background-color: ${progressBarColor};"></div>
</div>
<details>
<summary>Tareas</summary>
<ul class="task-list">${project.tasks.map(task => `
<li>${task.name} — ${task.status !== "Finalizado" ? `<button onclick="completeTask(${index}, '${task.name}')">Finalizar Tarea</button>` : ''}
</li>`).join('')}
</ul>
</details>
<button onclick="deleteProject(${index})">Eliminar Proyecto</button>`;
projectsContainer.appendChild(projectDiv);
});
}
function updateProjectSelection() {
const projectSelection = document.getElementById("projectSelection");
projectSelection.innerHTML = "";
projectSelection.disabled = projects.length === 0;
projects.forEach((project, index) => {
const option = document.createElement("option");
option.value = index;
option.text = project.name;
projectSelection.appendChild(option);
});
}
function clearProjects() {
if(confirm("Esta seguro que quiere eliminar todos los proyectos?")){
projects = [];
renderProjects();
updateProjectSelection();
}
}
function deleteProject(index) {
if (confirm("¿Estás seguro de que deseas eliminar este proyecto?")) {
projects.splice(index, 1);
renderProjects();
updateProjectSelection();
}
}
function completeTask(projectIndex, taskName) {
const project = projects[projectIndex];
const taskIndex = project.tasks.findIndex((task) => task.name === taskName);
if (taskIndex !== -1 && project.tasks[taskIndex].status !== "Finalizado") {
project.tasks[taskIndex].status = "Finalizado";
project.completedTasks++;
updateProgressBar(projectIndex);
renderProjects();
}
}
function saveProjects() {
localStorage.setItem('projects', JSON.stringify(projects));
}
const savedProjects = localStorage.getItem("projects");
if (savedProjects) {
try {
projects = JSON.parse(savedProjects);
} catch (error) {
console.error("Error al analizar los datos guardados:", error);
projects = [];
}
renderProjects();
updateProjectSelection();
projects.forEach((project, index) => updateProgressBar(index));
}
</script>
</body>
</html>
Last updated