JairoDanielMT commited on
Commit
2c991e5
verified
1 Parent(s): 5f1e831

Upload 12 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ static/uploads/1_mermaid-diagram-2025-06-25-183414.png filter=lfs diff=lfs merge=lfs -text
app.py ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import (
2
+ Flask,
3
+ render_template,
4
+ redirect,
5
+ url_for,
6
+ flash,
7
+ request,
8
+ send_from_directory,
9
+ )
10
+ from werkzeug.utils import secure_filename
11
+ from datetime import datetime
12
+ import os
13
+ from models import db, User, Project, Task, PMBOK_PROCESSES
14
+ from config import Config
15
+ from flask_login import (
16
+ LoginManager,
17
+ login_user,
18
+ logout_user,
19
+ login_required,
20
+ current_user,
21
+ )
22
+
23
+ app = Flask(__name__)
24
+ app.config.from_object(Config)
25
+
26
+ # Inicializaci贸n de extensiones
27
+ db.init_app(app)
28
+ login_manager = LoginManager()
29
+ login_manager.init_app(app)
30
+ login_manager.login_view = "login"
31
+
32
+
33
+ @login_manager.user_loader
34
+ def load_user(id):
35
+ return User.query.get(int(id))
36
+
37
+
38
+ def allowed_file(filename):
39
+ ALLOWED_EXTENSIONS = {"pdf", "docx", "jpg", "jpeg", "png"}
40
+ return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS
41
+
42
+
43
+ @app.route("/")
44
+ @login_required
45
+ def index():
46
+ projects = Project.query.filter_by(user_id=current_user.id).all()
47
+ return render_template("index.html", projects=projects)
48
+
49
+
50
+ @app.route("/project/new", methods=["GET", "POST"])
51
+ @login_required
52
+ def create_project():
53
+ if request.method == "POST":
54
+ name = request.form.get("name")
55
+ description = request.form.get("description")
56
+ start_date = datetime.strptime(request.form.get("start_date"), "%Y-%m-%d")
57
+ end_date = (
58
+ datetime.strptime(request.form.get("end_date"), "%Y-%m-%d")
59
+ if request.form.get("end_date")
60
+ else None
61
+ )
62
+
63
+ project = Project(
64
+ name=name,
65
+ description=description,
66
+ start_date=start_date,
67
+ end_date=end_date,
68
+ user_id=current_user.id,
69
+ )
70
+ db.session.add(project)
71
+
72
+ # Crear las 49 tareas PMBOK para el proyecto
73
+ for process in PMBOK_PROCESSES:
74
+ task = Task(
75
+ process_name=process["name"],
76
+ process_group=process["group"],
77
+ knowledge_area=process["area"],
78
+ project=project,
79
+ )
80
+ db.session.add(task)
81
+
82
+ db.session.commit()
83
+ flash("Proyecto creado exitosamente", "success")
84
+ return redirect(url_for("index"))
85
+
86
+ return render_template("create_project.html")
87
+
88
+
89
+ @app.route("/project/<int:project_id>")
90
+ @login_required
91
+ def project_detail(project_id):
92
+ project = Project.query.get_or_404(project_id)
93
+ if project.user_id != current_user.id:
94
+ flash("No tienes permiso para ver este proyecto", "danger")
95
+ return redirect(url_for("index"))
96
+ return render_template("project.html", project=project)
97
+
98
+
99
+ @app.route("/task/update/<int:task_id>", methods=["POST"])
100
+ @login_required
101
+ def update_task(task_id):
102
+ task = Task.query.get_or_404(task_id)
103
+ if task.project.user_id != current_user.id:
104
+ flash("No tienes permiso para modificar esta tarea", "danger")
105
+ return redirect(url_for("index"))
106
+
107
+ status = request.form.get("status")
108
+ file = request.files.get("evidence")
109
+
110
+ if status == "Terminado" and not (file or task.evidence_file):
111
+ flash(
112
+ "Debes subir una evidencia para marcar la tarea como terminada", "warning"
113
+ )
114
+ return redirect(url_for("project_detail", project_id=task.project_id))
115
+
116
+ if file and allowed_file(file.filename):
117
+ filename = secure_filename(f"{task.id}_{file.filename}")
118
+ file.save(os.path.join(app.config["UPLOAD_FOLDER"], filename))
119
+ task.evidence_file = filename
120
+
121
+ task.status = status
122
+ if status == "Terminado":
123
+ task.completion_date = datetime.utcnow()
124
+
125
+ db.session.commit()
126
+ flash("Tarea actualizada exitosamente", "success")
127
+ return redirect(url_for("project_detail", project_id=task.project_id))
128
+
129
+
130
+ @app.route("/uploads/<filename>")
131
+ @login_required
132
+ def download_file(filename):
133
+ return send_from_directory(app.config["UPLOAD_FOLDER"], filename)
134
+
135
+
136
+ @app.route("/login", methods=["GET", "POST"])
137
+ def login():
138
+ if current_user.is_authenticated:
139
+ return redirect(url_for("index"))
140
+
141
+ if request.method == "POST":
142
+ user = User.query.filter_by(username=request.form["username"]).first()
143
+ if user and user.check_password(request.form["password"]):
144
+ login_user(user)
145
+ return redirect(url_for("index"))
146
+ flash("Usuario o contrase帽a incorrectos", "danger")
147
+
148
+ return render_template("login.html")
149
+
150
+
151
+ @app.route("/register", methods=["GET", "POST"])
152
+ def register():
153
+ if current_user.is_authenticated:
154
+ return redirect(url_for("index"))
155
+
156
+ if request.method == "POST":
157
+ if User.query.filter_by(username=request.form["username"]).first():
158
+ flash("El nombre de usuario ya existe", "danger")
159
+ return redirect(url_for("register"))
160
+
161
+ user = User(username=request.form["username"])
162
+ user.set_password(request.form["password"])
163
+ db.session.add(user)
164
+ db.session.commit()
165
+
166
+ flash("Registro exitoso. Por favor inicia sesi贸n", "success")
167
+ return redirect(url_for("login"))
168
+
169
+ return render_template("register.html")
170
+
171
+
172
+ @app.route("/logout")
173
+ @login_required
174
+ def logout():
175
+ logout_user()
176
+ return redirect(url_for("login"))
177
+
178
+
179
+ if __name__ == "__main__":
180
+ with app.app_context():
181
+ db.create_all()
182
+ app.run(debug=True)
config.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+
4
+ class Config:
5
+ # Configuraci贸n b谩sica
6
+ SECRET_KEY = os.environ.get("SECRET_KEY") or "dev-key-very-secret"
7
+
8
+ # Configuraci贸n de SQLAlchemy
9
+ SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL") or "sqlite:///pmbok.db"
10
+ SQLALCHEMY_TRACK_MODIFICATIONS = False
11
+
12
+ # Configuraci贸n de carga de archivos
13
+ UPLOAD_FOLDER = os.path.join(
14
+ os.path.dirname(os.path.abspath(__file__)), "static/uploads"
15
+ )
16
+ MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB max file size
instance/pmbok.db ADDED
Binary file (20.5 kB). View file
 
models.py ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask_sqlalchemy import SQLAlchemy
2
+ from datetime import datetime
3
+ from werkzeug.security import generate_password_hash, check_password_hash
4
+ from flask_login import UserMixin
5
+
6
+ db = SQLAlchemy()
7
+
8
+
9
+ class User(UserMixin, db.Model):
10
+ id = db.Column(db.Integer, primary_key=True)
11
+ username = db.Column(db.String(80), unique=True, nullable=False)
12
+ password_hash = db.Column(db.String(128))
13
+ projects = db.relationship("Project", backref="owner", lazy=True)
14
+
15
+ def set_password(self, password):
16
+ self.password_hash = generate_password_hash(password)
17
+
18
+ def check_password(self, password):
19
+ return check_password_hash(self.password_hash, password)
20
+
21
+
22
+ class Project(db.Model):
23
+ id = db.Column(db.Integer, primary_key=True)
24
+ name = db.Column(db.String(100), nullable=False)
25
+ description = db.Column(db.Text)
26
+ start_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
27
+ end_date = db.Column(db.DateTime)
28
+ user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
29
+ tasks = db.relationship(
30
+ "Task", backref="project", lazy=True, cascade="all, delete-orphan"
31
+ )
32
+
33
+ @property
34
+ def progress(self):
35
+ if not self.tasks:
36
+ return 0
37
+ completed_tasks = sum(1 for task in self.tasks if task.status == "Terminado")
38
+ return round((completed_tasks / len(self.tasks)) * 100, 2)
39
+
40
+
41
+ class Task(db.Model):
42
+ id = db.Column(db.Integer, primary_key=True)
43
+ process_name = db.Column(db.String(200), nullable=False)
44
+ process_group = db.Column(db.String(50), nullable=False)
45
+ knowledge_area = db.Column(db.String(50), nullable=False)
46
+ status = db.Column(db.String(20), default="Pendiente")
47
+ evidence_file = db.Column(db.String(255))
48
+ completion_date = db.Column(db.DateTime)
49
+ project_id = db.Column(db.Integer, db.ForeignKey("project.id"), nullable=False)
50
+
51
+
52
+ # Lista de procesos PMBOK 6th Edition
53
+ PMBOK_PROCESSES = [
54
+ # Inicio
55
+ {
56
+ "group": "Inicio",
57
+ "area": "Integraci贸n",
58
+ "name": "Desarrollar Acta de Constituci贸n del Proyecto",
59
+ },
60
+ {"group": "Inicio", "area": "Interesados", "name": "Identificar a los Interesados"},
61
+ # Planificaci贸n
62
+ {
63
+ "group": "Planificaci贸n",
64
+ "area": "Integraci贸n",
65
+ "name": "Desarrollar el Plan para la Direcci贸n del Proyecto",
66
+ },
67
+ {
68
+ "group": "Planificaci贸n",
69
+ "area": "Alcance",
70
+ "name": "Planificar la Gesti贸n del Alcance",
71
+ },
72
+ {"group": "Planificaci贸n", "area": "Alcance", "name": "Recopilar Requisitos"},
73
+ {"group": "Planificaci贸n", "area": "Alcance", "name": "Definir el Alcance"},
74
+ {"group": "Planificaci贸n", "area": "Alcance", "name": "Crear la EDT/WBS"},
75
+ {
76
+ "group": "Planificaci贸n",
77
+ "area": "Tiempo",
78
+ "name": "Planificar la Gesti贸n del Cronograma",
79
+ },
80
+ {"group": "Planificaci贸n", "area": "Tiempo", "name": "Definir las Actividades"},
81
+ {"group": "Planificaci贸n", "area": "Tiempo", "name": "Secuenciar las Actividades"},
82
+ {
83
+ "group": "Planificaci贸n",
84
+ "area": "Tiempo",
85
+ "name": "Estimar la Duraci贸n de las Actividades",
86
+ },
87
+ {"group": "Planificaci贸n", "area": "Tiempo", "name": "Desarrollar el Cronograma"},
88
+ {
89
+ "group": "Planificaci贸n",
90
+ "area": "Costos",
91
+ "name": "Planificar la Gesti贸n de Costos",
92
+ },
93
+ {"group": "Planificaci贸n", "area": "Costos", "name": "Estimar los Costos"},
94
+ {"group": "Planificaci贸n", "area": "Costos", "name": "Determinar el Presupuesto"},
95
+ {
96
+ "group": "Planificaci贸n",
97
+ "area": "Calidad",
98
+ "name": "Planificar la Gesti贸n de Calidad",
99
+ },
100
+ {
101
+ "group": "Planificaci贸n",
102
+ "area": "Recursos Humanos",
103
+ "name": "Planificar la Gesti贸n de Recursos",
104
+ },
105
+ {
106
+ "group": "Planificaci贸n",
107
+ "area": "Comunicaciones",
108
+ "name": "Planificar la Gesti贸n de Comunicaciones",
109
+ },
110
+ {
111
+ "group": "Planificaci贸n",
112
+ "area": "Riesgos",
113
+ "name": "Planificar la Gesti贸n de Riesgos",
114
+ },
115
+ {"group": "Planificaci贸n", "area": "Riesgos", "name": "Identificar los Riesgos"},
116
+ {
117
+ "group": "Planificaci贸n",
118
+ "area": "Riesgos",
119
+ "name": "Realizar An谩lisis Cualitativo de Riesgos",
120
+ },
121
+ {
122
+ "group": "Planificaci贸n",
123
+ "area": "Riesgos",
124
+ "name": "Realizar An谩lisis Cuantitativo de Riesgos",
125
+ },
126
+ {
127
+ "group": "Planificaci贸n",
128
+ "area": "Riesgos",
129
+ "name": "Planificar la Respuesta a los Riesgos",
130
+ },
131
+ {
132
+ "group": "Planificaci贸n",
133
+ "area": "Adquisiciones",
134
+ "name": "Planificar la Gesti贸n de las Adquisiciones",
135
+ },
136
+ {
137
+ "group": "Planificaci贸n",
138
+ "area": "Interesados",
139
+ "name": "Planificar la Participaci贸n de los Interesados",
140
+ },
141
+ # Ejecuci贸n
142
+ {
143
+ "group": "Ejecuci贸n",
144
+ "area": "Integraci贸n",
145
+ "name": "Dirigir y Gestionar el Trabajo del Proyecto",
146
+ },
147
+ {
148
+ "group": "Ejecuci贸n",
149
+ "area": "Integraci贸n",
150
+ "name": "Gestionar el Conocimiento del Proyecto",
151
+ },
152
+ {"group": "Ejecuci贸n", "area": "Calidad", "name": "Gestionar la Calidad"},
153
+ {"group": "Ejecuci贸n", "area": "Recursos Humanos", "name": "Adquirir Recursos"},
154
+ {"group": "Ejecuci贸n", "area": "Recursos Humanos", "name": "Desarrollar el Equipo"},
155
+ {"group": "Ejecuci贸n", "area": "Recursos Humanos", "name": "Dirigir el Equipo"},
156
+ {
157
+ "group": "Ejecuci贸n",
158
+ "area": "Comunicaciones",
159
+ "name": "Gestionar las Comunicaciones",
160
+ },
161
+ {
162
+ "group": "Ejecuci贸n",
163
+ "area": "Riesgos",
164
+ "name": "Implementar la Respuesta a los Riesgos",
165
+ },
166
+ {
167
+ "group": "Ejecuci贸n",
168
+ "area": "Adquisiciones",
169
+ "name": "Efectuar las Adquisiciones",
170
+ },
171
+ {
172
+ "group": "Ejecuci贸n",
173
+ "area": "Interesados",
174
+ "name": "Gestionar la Participaci贸n de los Interesados",
175
+ },
176
+ # Monitoreo y Control
177
+ {
178
+ "group": "Monitoreo",
179
+ "area": "Integraci贸n",
180
+ "name": "Monitorear y Controlar el Trabajo del Proyecto",
181
+ },
182
+ {
183
+ "group": "Monitoreo",
184
+ "area": "Integraci贸n",
185
+ "name": "Realizar el Control Integrado de Cambios",
186
+ },
187
+ {"group": "Monitoreo", "area": "Alcance", "name": "Validar el Alcance"},
188
+ {"group": "Monitoreo", "area": "Alcance", "name": "Controlar el Alcance"},
189
+ {"group": "Monitoreo", "area": "Tiempo", "name": "Controlar el Cronograma"},
190
+ {"group": "Monitoreo", "area": "Costos", "name": "Controlar los Costos"},
191
+ {"group": "Monitoreo", "area": "Calidad", "name": "Controlar la Calidad"},
192
+ {
193
+ "group": "Monitoreo",
194
+ "area": "Recursos Humanos",
195
+ "name": "Controlar los Recursos",
196
+ },
197
+ {
198
+ "group": "Monitoreo",
199
+ "area": "Comunicaciones",
200
+ "name": "Monitorear las Comunicaciones",
201
+ },
202
+ {"group": "Monitoreo", "area": "Riesgos", "name": "Monitorear los Riesgos"},
203
+ {
204
+ "group": "Monitoreo",
205
+ "area": "Adquisiciones",
206
+ "name": "Controlar las Adquisiciones",
207
+ },
208
+ {
209
+ "group": "Monitoreo",
210
+ "area": "Interesados",
211
+ "name": "Monitorear la Participaci贸n de los Interesados",
212
+ },
213
+ # Cierre
214
+ {"group": "Cierre", "area": "Integraci贸n", "name": "Cerrar el Proyecto o Fase"},
215
+ ]
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Flask==3.0.0
2
+ Flask-SQLAlchemy==3.1.1
3
+ Flask-Login==0.6.3
4
+ Werkzeug==3.0.1
5
+ python-dotenv==1.0.0
static/uploads/1_mermaid-diagram-2025-06-25-183414.png ADDED

Git LFS Details

  • SHA256: 1776de3df14036d947874039ad6d6f04500b08e68510140324d878bd6b6c3e6a
  • Pointer size: 131 Bytes
  • Size of remote file: 302 kB
templates/base.html ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="es">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>{% block title %}PMBOK Tracker{% endblock %}</title>
8
+ <!-- Tailwind CSS -->
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+ <!-- Bootstrap CSS -->
11
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
12
+ </head>
13
+
14
+ <body class="bg-gray-100">
15
+ <nav class="bg-blue-600 text-white shadow-lg mb-4">
16
+ <div class="container mx-auto px-4 py-3">
17
+ <div class="flex justify-between items-center">
18
+ <a href="{{ url_for('index') }}" class="text-xl font-bold">PMBOK Tracker</a>
19
+ <div>
20
+ {% if current_user.is_authenticated %}
21
+ <span class="mr-4">{{ current_user.username }}</span>
22
+ <a href="{{ url_for('logout') }}" class="text-white hover:text-gray-200">Cerrar Sesi贸n</a>
23
+ {% else %}
24
+ <a href="{{ url_for('login') }}" class="text-white hover:text-gray-200">Iniciar Sesi贸n</a>
25
+ {% endif %}
26
+ </div>
27
+ </div>
28
+ </div>
29
+ </nav>
30
+
31
+ <div class="container mx-auto px-4">
32
+ {% with messages = get_flashed_messages(with_categories=true) %}
33
+ {% if messages %}
34
+ {% for category, message in messages %}
35
+ <div class="alert alert-{{ category }} mb-4">
36
+ {{ message }}
37
+ </div>
38
+ {% endfor %}
39
+ {% endif %}
40
+ {% endwith %}
41
+
42
+ {% block content %}{% endblock %}
43
+ </div>
44
+
45
+ <!-- Bootstrap JS Bundle -->
46
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
47
+ </body>
48
+
49
+ </html>
templates/create_project.html ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <div class="max-w-2xl mx-auto bg-white rounded-lg shadow-md p-6">
5
+ <div class="flex justify-between items-center mb-6">
6
+ <h2 class="text-2xl font-bold text-gray-800">Crear Nuevo Proyecto</h2>
7
+ <a href="{{ url_for('index') }}" class="btn btn-secondary">Volver</a>
8
+ </div>
9
+
10
+ <form method="POST" action="{{ url_for('create_project') }}">
11
+ <div class="mb-4">
12
+ <label for="name" class="block text-gray-700 text-sm font-bold mb-2">Nombre del Proyecto</label>
13
+ <input type="text" id="name" name="name" required class="form-control"
14
+ placeholder="Ingresa el nombre del proyecto">
15
+ </div>
16
+
17
+ <div class="mb-4">
18
+ <label for="description" class="block text-gray-700 text-sm font-bold mb-2">Descripci贸n</label>
19
+ <textarea id="description" name="description" rows="4" class="form-control"
20
+ placeholder="Describe el proyecto"></textarea>
21
+ </div>
22
+
23
+ <div class="mb-4">
24
+ <label for="start_date" class="block text-gray-700 text-sm font-bold mb-2">Fecha de Inicio</label>
25
+ <input type="date" id="start_date" name="start_date" required class="form-control">
26
+ </div>
27
+
28
+ <div class="mb-6">
29
+ <label for="end_date" class="block text-gray-700 text-sm font-bold mb-2">Fecha de Fin (Opcional)</label>
30
+ <input type="date" id="end_date" name="end_date" class="form-control">
31
+ </div>
32
+
33
+ <div class="flex justify-end">
34
+ <button type="submit" class="btn btn-primary">
35
+ Crear Proyecto
36
+ </button>
37
+ </div>
38
+ </form>
39
+ </div>
40
+ {% endblock %}
templates/index.html ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <div class="bg-white rounded-lg shadow-md p-6 mb-6">
5
+ <div class="flex justify-between items-center mb-6">
6
+ <h1 class="text-2xl font-bold text-gray-800">Proyectos PMBOK</h1>
7
+ <a href="{{ url_for('create_project') }}" class="btn btn-primary">Nuevo Proyecto</a>
8
+ </div>
9
+
10
+ {% if projects %}
11
+ <div class="overflow-x-auto">
12
+ <table class="table table-striped">
13
+ <thead class="bg-gray-50">
14
+ <tr>
15
+ <th scope="col">Nombre</th>
16
+ <th scope="col">Descripci贸n</th>
17
+ <th scope="col">Fecha Inicio</th>
18
+ <th scope="col">Fecha Fin</th>
19
+ <th scope="col">Progreso</th>
20
+ <th scope="col">Acciones</th>
21
+ </tr>
22
+ </thead>
23
+ <tbody>
24
+ {% for project in projects %}
25
+ <tr>
26
+ <td>{{ project.name }}</td>
27
+ <td>{{ project.description[:100] }}...</td>
28
+ <td>{{ project.start_date.strftime('%d/%m/%Y') }}</td>
29
+ <td>{{ project.end_date.strftime('%d/%m/%Y') if project.end_date else 'No definida' }}</td>
30
+ <td>
31
+ <div class="progress">
32
+ <div class="progress-bar" role="progressbar" style="width: {{ project.progress }}%"
33
+ aria-valuenow="{{ project.progress }}" aria-valuemin="0" aria-valuemax="100">
34
+ {{ project.progress }}%
35
+ </div>
36
+ </div>
37
+ </td>
38
+ <td>
39
+ <a href="{{ url_for('project_detail', project_id=project.id) }}" class="btn btn-sm btn-info">Ver
40
+ Detalle</a>
41
+ </td>
42
+ </tr>
43
+ {% endfor %}
44
+ </tbody>
45
+ </table>
46
+ </div>
47
+ {% else %}
48
+ <div class="text-center py-6">
49
+ <p class="text-gray-600">No hay proyectos registrados.</p>
50
+ <a href="{{ url_for('create_project') }}" class="btn btn-primary mt-4">Crear Primer Proyecto</a>
51
+ </div>
52
+ {% endif %}
53
+ </div>
54
+ {% endblock %}
templates/login.html ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <div class="max-w-md mx-auto bg-white rounded-lg shadow-md p-6">
5
+ <h2 class="text-2xl font-bold text-gray-800 mb-6 text-center">Iniciar Sesi贸n</h2>
6
+
7
+ <form method="POST" action="{{ url_for('login') }}">
8
+ <div class="mb-4">
9
+ <label for="username" class="block text-gray-700 text-sm font-bold mb-2">Usuario</label>
10
+ <input type="text" id="username" name="username" required class="form-control"
11
+ placeholder="Ingresa tu nombre de usuario">
12
+ </div>
13
+
14
+ <div class="mb-6">
15
+ <label for="password" class="block text-gray-700 text-sm font-bold mb-2">Contrase帽a</label>
16
+ <input type="password" id="password" name="password" required class="form-control"
17
+ placeholder="Ingresa tu contrase帽a">
18
+ </div>
19
+
20
+ <div class="flex items-center justify-between">
21
+ <button type="submit" class="btn btn-primary w-full">
22
+ Iniciar Sesi贸n
23
+ </button>
24
+ </div>
25
+ </form>
26
+
27
+ <div class="mt-4 text-center">
28
+ <p class="text-gray-600">驴No tienes una cuenta?
29
+ <a href="{{ url_for('register') }}" class="text-blue-500 hover:text-blue-700">
30
+ Reg铆strate
31
+ </a>
32
+ </p>
33
+ </div>
34
+ </div>
35
+ {% endblock %}
templates/project.html ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <div class="bg-white rounded-lg shadow-md p-6">
5
+ <div class="mb-6">
6
+ <div class="flex justify-between items-center">
7
+ <h1 class="text-2xl font-bold text-gray-800">{{ project.name }}</h1>
8
+ <a href="{{ url_for('index') }}" class="btn btn-secondary">Volver a Proyectos</a>
9
+ </div>
10
+ <p class="text-gray-600 mt-2">{{ project.description }}</p>
11
+ <div class="mt-4">
12
+ <strong>Progreso General:</strong>
13
+ <div class="progress mt-2">
14
+ <div class="progress-bar" role="progressbar" style="width: {{ project.progress }}%"
15
+ aria-valuenow="{{ project.progress }}" aria-valuemin="0" aria-valuemax="100">
16
+ {{ project.progress }}%
17
+ </div>
18
+ </div>
19
+ </div>
20
+ </div>
21
+
22
+ <div class="mb-4">
23
+ <div class="btn-group" role="group">
24
+ {% for group in ["Inicio", "Planificaci贸n", "Ejecuci贸n", "Monitoreo", "Cierre"] %}
25
+ <button type="button" class="btn btn-outline-primary filter-btn" data-group="{{ group }}">
26
+ {{ group }}
27
+ </button>
28
+ {% endfor %}
29
+ </div>
30
+ </div>
31
+
32
+ <div class="overflow-x-auto">
33
+ <table class="table table-striped">
34
+ <thead class="bg-gray-50">
35
+ <tr>
36
+ <th>Proceso</th>
37
+ <th>Grupo</th>
38
+ <th>脕rea</th>
39
+ <th>Estado</th>
40
+ <th>Evidencia</th>
41
+ <th>Acciones</th>
42
+ </tr>
43
+ </thead>
44
+ <tbody>
45
+ {% for task in project.tasks %}
46
+ <tr class="task-row" data-group="{{ task.process_group }}">
47
+ <td>{{ task.process_name }}</td>
48
+ <td>{{ task.process_group }}</td>
49
+ <td>{{ task.knowledge_area }}</td>
50
+ <td>
51
+ <span
52
+ class="badge {% if task.status == 'Terminado' %}bg-success{% elif task.status == 'En Proceso' %}bg-warning{% else %}bg-secondary{% endif %}">
53
+ {{ task.status }}
54
+ </span>
55
+ </td>
56
+ <td>
57
+ {% if task.evidence_file %}
58
+ <a href="{{ url_for('download_file', filename=task.evidence_file) }}"
59
+ class="btn btn-sm btn-link">
60
+ Descargar
61
+ </a>
62
+ {% else %}
63
+ <span class="text-muted">Sin evidencia</span>
64
+ {% endif %}
65
+ </td>
66
+ <td>
67
+ <button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal"
68
+ data-bs-target="#taskModal{{ task.id }}">
69
+ Actualizar
70
+ </button>
71
+
72
+ <!-- Modal para cada tarea -->
73
+ <div class="modal fade" id="taskModal{{ task.id }}" tabindex="-1">
74
+ <div class="modal-dialog">
75
+ <div class="modal-content">
76
+ <div class="modal-header">
77
+ <h5 class="modal-title">Actualizar Tarea</h5>
78
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
79
+ </div>
80
+ <form action="{{ url_for('update_task', task_id=task.id) }}" method="POST"
81
+ enctype="multipart/form-data">
82
+ <div class="modal-body">
83
+ <div class="mb-3">
84
+ <label class="form-label">Estado</label>
85
+ <select name="status" class="form-select" required>
86
+ <option value="Pendiente" {% if task.status=='Pendiente'
87
+ %}selected{% endif %}>Pendiente</option>
88
+ <option value="En Proceso" {% if task.status=='En Proceso'
89
+ %}selected{% endif %}>En Proceso</option>
90
+ <option value="Terminado" {% if task.status=='Terminado'
91
+ %}selected{% endif %}>Terminado</option>
92
+ </select>
93
+ </div>
94
+ <div class="mb-3">
95
+ <label class="form-label">Evidencia (PDF, DOCX, Imagen)</label>
96
+ <input type="file" name="evidence" class="form-control"
97
+ accept=".pdf,.docx,.jpg,.jpeg,.png">
98
+ </div>
99
+ </div>
100
+ <div class="modal-footer">
101
+ <button type="button" class="btn btn-secondary"
102
+ data-bs-dismiss="modal">Cancelar</button>
103
+ <button type="submit" class="btn btn-primary">Guardar</button>
104
+ </div>
105
+ </form>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ </td>
110
+ </tr>
111
+ {% endfor %}
112
+ </tbody>
113
+ </table>
114
+ </div>
115
+ </div>
116
+
117
+ <script>
118
+ document.addEventListener('DOMContentLoaded', function () {
119
+ const filterButtons = document.querySelectorAll('.filter-btn');
120
+ const taskRows = document.querySelectorAll('.task-row');
121
+
122
+ filterButtons.forEach(button => {
123
+ button.addEventListener('click', function () {
124
+ const group = this.dataset.group;
125
+
126
+ // Toggle active state of buttons
127
+ filterButtons.forEach(btn => btn.classList.remove('active'));
128
+ this.classList.add('active');
129
+
130
+ // Filter rows
131
+ taskRows.forEach(row => {
132
+ if (group === 'all' || row.dataset.group === group) {
133
+ row.style.display = '';
134
+ } else {
135
+ row.style.display = 'none';
136
+ }
137
+ });
138
+ });
139
+ });
140
+ });
141
+ </script>
142
+ {% endblock %}
templates/register.html ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <div class="max-w-md mx-auto bg-white rounded-lg shadow-md p-6">
5
+ <h2 class="text-2xl font-bold text-gray-800 mb-6 text-center">Registro</h2>
6
+
7
+ <form method="POST" action="{{ url_for('register') }}">
8
+ <div class="mb-4">
9
+ <label for="username" class="block text-gray-700 text-sm font-bold mb-2">Usuario</label>
10
+ <input type="text" id="username" name="username" required class="form-control"
11
+ placeholder="Elige un nombre de usuario">
12
+ </div>
13
+
14
+ <div class="mb-6">
15
+ <label for="password" class="block text-gray-700 text-sm font-bold mb-2">Contrase帽a</label>
16
+ <input type="password" id="password" name="password" required class="form-control"
17
+ placeholder="Elige una contrase帽a segura">
18
+ </div>
19
+
20
+ <div class="flex items-center justify-between">
21
+ <button type="submit" class="btn btn-primary w-full">
22
+ Registrarse
23
+ </button>
24
+ </div>
25
+ </form>
26
+
27
+ <div class="mt-4 text-center">
28
+ <p class="text-gray-600">驴Ya tienes una cuenta?
29
+ <a href="{{ url_for('login') }}" class="text-blue-500 hover:text-blue-700">
30
+ Inicia Sesi贸n
31
+ </a>
32
+ </p>
33
+ </div>
34
+ </div>
35
+ {% endblock %}