From b127553151c2eb5352be50f44b08b39904cc30db Mon Sep 17 00:00:00 2001 From: Tatiana Villa Ema Date: Mon, 15 Jun 2026 10:09:30 +0200 Subject: [PATCH] actualizaciones post examen TAI --- .gitignore | 2 + src/main/resources/static/css/planning.css | 236 +++++++++++ src/main/resources/templates/planning.html | 451 ++++++++++++++------- 3 files changed, 543 insertions(+), 146 deletions(-) diff --git a/.gitignore b/.gitignore index 915f2a2..8b83b04 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,5 @@ build/ .venv + +.fake \ No newline at end of file diff --git a/src/main/resources/static/css/planning.css b/src/main/resources/static/css/planning.css index e4b6568..2a96542 100644 --- a/src/main/resources/static/css/planning.css +++ b/src/main/resources/static/css/planning.css @@ -201,3 +201,239 @@ h1 { font-size: 18pt; text-align: center; margin-bottom: 0.2em; color: var(--acc .hoy-label, .btn-reset-planning { display: none !important; } .tema-btn.completado { text-decoration: none; opacity: 1; color: inherit !important; } } + + +/* ── Planning interactivo 20260615 ───────────────────── */ + +.planning-container { + max-width: 1200px; + margin: 20px auto; + padding: 0 20px; + } + + .planning-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 30px; + flex-wrap: wrap; + gap: 15px; + } + + .month-nav { + display: flex; + align-items: center; + gap: 15px; + } + + .month-title { + font-size: 24px; + font-weight: bold; + min-width: 200px; + text-align: center; + } + + .nav-btn { + background: #007bff; + color: white; + border: none; + padding: 8px 15px; + border-radius: 5px; + cursor: pointer; + font-size: 16px; + transition: background 0.3s; + } + + .nav-btn:hover { + background: #0056b3; + } + + .add-task-form { + display: flex; + gap: 10px; + margin-bottom: 20px; + flex-wrap: wrap; + } + + .add-task-form input { + flex: 1; + min-width: 200px; + padding: 10px; + border: 1px solid #ddd; + border-radius: 5px; + font-size: 14px; + } + + .add-task-form button { + background: #28a745; + color: white; + border: none; + padding: 10px 20px; + border-radius: 5px; + cursor: pointer; + font-size: 14px; + transition: background 0.3s; + } + + .add-task-form button:hover { + background: #218838; + } + + .reset-btn { + background: #dc3545; + color: white; + border: none; + padding: 8px 15px; + border-radius: 5px; + cursor: pointer; + font-size: 14px; + transition: background 0.3s; + } + + .reset-btn:hover { + background: #c82333; + } + + .calendar-grid { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 10px; + margin-top: 20px; + } + + .day-header { + text-align: center; + font-weight: bold; + padding: 10px; + background: #f0f0f0; + border-radius: 5px; + } + + .day-cell { + border: 1px solid #ddd; + border-radius: 5px; + padding: 10px; + min-height: 150px; + background: white; + cursor: move; + transition: all 0.3s; + position: relative; + } + + .day-cell.empty { + background: #f9f9f9; + cursor: default; + } + + .day-cell.other-month { + background: #f0f0f0; + opacity: 0.5; + cursor: default; + } + + .day-cell.today { + background: #e7f3ff; + border: 2px solid #007bff; + } + + .day-cell.weekend { + background: #fff8f0; + } + + .day-cell:hover:not(.empty):not(.other-month) { + box-shadow: 0 2px 8px rgba(0,0,0,0.15); + transform: translateY(-2px); + } + + .day-number { + font-weight: bold; + margin-bottom: 8px; + color: #333; + } + + .day-number.weekend { + color: #d9534f; + } + + .tasks-list { + display: flex; + flex-direction: column; + gap: 5px; + } + + .task-item { + background: #007bff; + color: white; + padding: 6px 8px; + border-radius: 3px; + font-size: 12px; + display: flex; + justify-content: space-between; + align-items: center; + word-break: break-word; + cursor: grab; + transition: all 0.2s; + } + + .task-item:active { + cursor: grabbing; + opacity: 0.8; + } + + .task-item.dragging { + opacity: 0.5; + } + + .task-item:hover { + background: #0056b3; + } + + .task-delete-btn { + background: none; + border: none; + color: white; + cursor: pointer; + font-size: 14px; + padding: 0 4px; + margin-left: 4px; + transition: all 0.2s; + } + + .task-delete-btn:hover { + color: #ffcccc; + transform: scale(1.2); + } + + .drop-zone { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border-radius: 5px; + display: none; + border: 2px dashed #007bff; + background: rgba(0, 123, 255, 0.1); + } + + .day-cell.drag-over .drop-zone { + display: block; + } + + @media (max-width: 768px) { + .calendar-grid { + grid-template-columns: repeat(7, 1fr); + gap: 5px; + } + + .day-cell { + min-height: 120px; + padding: 8px; + font-size: 12px; + } + + .task-item { + font-size: 11px; + padding: 4px 6px; + } + } \ No newline at end of file diff --git a/src/main/resources/templates/planning.html b/src/main/resources/templates/planning.html index 47593fd..86a481f 100644 --- a/src/main/resources/templates/planning.html +++ b/src/main/resources/templates/planning.html @@ -3,9 +3,8 @@ - Planning TAI — Mayo 2026 + Planning TAI — Agenda - @@ -32,155 +31,315 @@ -

Planning de repaso TAI

-

Mayo 2026 · Examen: sábado 23 de mayo - -

+
+

Mi Planning 2026

+ + +
+
+ +
+ +
+ +
- -
-
- LunMarMiéJueVieSábDom -
- -
+ +
+ + +
- -
-
- LunMarMiéJueVieSábDom + +
-
- - - - - - - -
-
- -
-
- LunMarMiéJueVieSábDom -
-
-
- 18 - Repaso
B1. Organización del Estado y Administración electrónica
B2. Tecnología básica
-
-
- 19 - Repaso
B3. Desarrollo de sistemas
B4. Sistemas y comunicaciones
-
-
- 20 - 🧪 Simulacro
Examen completo
con tiempo real
-
-
- 21 - Repaso de fallos
del simulacro
-
-
- 22 - Solo flashcards
Nada nuevo
Descansar pronto
-
-
- 23 - 🎯 EXAMEN -
-
-
-
+ + init() { + this.setupEventListeners(); + this.render(); + } + + setupEventListeners() { + document.getElementById('prevMonth').addEventListener('click', () => this.previousMonth()); + document.getElementById('nextMonth').addEventListener('click', () => this.nextMonth()); + document.getElementById('addTaskForm').addEventListener('submit', (e) => this.addTask(e)); + document.getElementById('resetBtn').addEventListener('click', () => this.resetAll()); + } + + previousMonth() { + this.currentDate.setMonth(this.currentDate.getMonth() - 1); + this.render(); + } + + nextMonth() { + this.currentDate.setMonth(this.currentDate.getMonth() + 1); + this.render(); + } + + render() { + this.renderMonthTitle(); + this.renderCalendar(); + } + + renderMonthTitle() { + const options = { month: 'long', year: 'numeric' }; + const title = this.currentDate.toLocaleDateString('es-ES', options); + document.getElementById('monthTitle').textContent = title.charAt(0).toUpperCase() + title.slice(1); + } + + renderCalendar() { + const grid = document.getElementById('calendarGrid'); + grid.innerHTML = ''; + + // Encabezados de días + const dayNames = ['Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb', 'Dom']; + dayNames.forEach(day => { + const header = document.createElement('div'); + header.className = 'day-header'; + header.textContent = day; + grid.appendChild(header); + }); + + // Obtener primer y último día del mes + const year = this.currentDate.getFullYear(); + const month = this.currentDate.getMonth(); + const firstDay = new Date(year, month, 1); + const lastDay = new Date(year, month + 1, 0); + + // Ajustar para que lunes sea 0 + let dayOfWeek = firstDay.getDay() - 1; + if (dayOfWeek === -1) dayOfWeek = 6; + + // Días del mes anterior + const prevMonthLastDay = new Date(year, month, 0).getDate(); + for (let i = dayOfWeek - 1; i >= 0; i--) { + const cell = this.createDayCell(prevMonthLastDay - i, 'other-month'); + grid.appendChild(cell); + } + + // Días del mes actual + for (let day = 1; day <= lastDay.getDate(); day++) { + const date = new Date(year, month, day); + const cell = this.createDayCell(day, '', date); + grid.appendChild(cell); + } + + // Días del próximo mes + const remainingCells = grid.children.length - 7; // 7 headers + const totalCells = Math.ceil(remainingCells / 7) * 7; + for (let day = 1; day <= totalCells - remainingCells; day++) { + const cell = this.createDayCell(day, 'other-month'); + grid.appendChild(cell); + } + } + + createDayCell(day, extraClass, date = null) { + const cell = document.createElement('div'); + cell.className = `day-cell ${extraClass}`; + + if (extraClass === 'other-month') { + cell.classList.add('empty'); + } else { + // Es día del mes actual + const isToday = date && this.isToday(date); + const isWeekend = date && (date.getDay() === 0 || date.getDay() === 6); + + if (isToday) cell.classList.add('today'); + if (isWeekend) cell.classList.add('weekend'); + + const dayNumber = document.createElement('div'); + dayNumber.className = 'day-number' + (isWeekend ? ' weekend' : ''); + dayNumber.textContent = day; + cell.appendChild(dayNumber); + + const dateKey = this.getDateKey(date); + const dayTasks = this.tasks[dateKey] || []; + + const tasksList = document.createElement('div'); + tasksList.className = 'tasks-list'; + + dayTasks.forEach(task => { + const taskEl = this.createTaskElement(task, dateKey); + tasksList.appendChild(taskEl); + }); + + cell.appendChild(tasksList); + + // Agregar zona de drop + const dropZone = document.createElement('div'); + dropZone.className = 'drop-zone'; + cell.appendChild(dropZone); + + // Event listeners para drag & drop + cell.addEventListener('dragover', (e) => this.handleDragOver(e)); + cell.addEventListener('drop', (e) => this.handleDrop(e, dateKey)); + cell.addEventListener('dragleave', (e) => this.handleDragLeave(e)); + } + + return cell; + } + + createTaskElement(task, dateKey) { + const taskEl = document.createElement('div'); + taskEl.className = 'task-item'; + taskEl.draggable = true; + taskEl.textContent = task.text; + + const deleteBtn = document.createElement('button'); + deleteBtn.type = 'button'; + deleteBtn.className = 'task-delete-btn'; + deleteBtn.textContent = '✕'; + deleteBtn.addEventListener('click', (e) => { + e.stopPropagation(); + this.deleteTask(task.id, dateKey); + }); + + taskEl.appendChild(deleteBtn); + + taskEl.addEventListener('dragstart', (e) => this.handleDragStart(e, task.id, dateKey)); + taskEl.addEventListener('dragend', (e) => this.handleDragEnd(e)); + + return taskEl; + } + + handleDragStart(e, taskId, fromDateKey) { + this.draggedTask = { taskId, fromDateKey }; + e.target.classList.add('dragging'); + e.dataTransfer.effectAllowed = 'move'; + } + + handleDragEnd(e) { + e.target.classList.remove('dragging'); + } + + handleDragOver(e) { + e.preventDefault(); + e.dataTransfer.dropEffect = 'move'; + e.target.closest('.day-cell').classList.add('drag-over'); + } + + handleDragLeave(e) { + e.target.closest('.day-cell').classList.remove('drag-over'); + } + + handleDrop(e, toDateKey) { + e.preventDefault(); + e.target.closest('.day-cell').classList.remove('drag-over'); + + if (!this.draggedTask) return; + + const { taskId, fromDateKey } = this.draggedTask; + this.moveTask(taskId, fromDateKey, toDateKey); + this.draggedTask = null; + } + + addTask(e) { + e.preventDefault(); + const input = document.getElementById('taskInput'); + const text = input.value.trim(); + + if (!text) return; + + const today = this.getDateKey(new Date()); + const task = { + id: Date.now(), + text: text, + date: today + }; + + if (!this.tasks[today]) { + this.tasks[today] = []; + } + + this.tasks[today].push(task); + this.saveTasks(); + input.value = ''; + this.render(); + } + + deleteTask(taskId, dateKey) { + if (this.tasks[dateKey]) { + this.tasks[dateKey] = this.tasks[dateKey].filter(t => t.id !== taskId); + if (this.tasks[dateKey].length === 0) { + delete this.tasks[dateKey]; + } + this.saveTasks(); + this.render(); + } + } + + moveTask(taskId, fromDateKey, toDateKey) { + if (!this.tasks[fromDateKey]) return; + + const taskIndex = this.tasks[fromDateKey].findIndex(t => t.id === taskId); + if (taskIndex === -1) return; + + const task = this.tasks[fromDateKey][taskIndex]; + this.tasks[fromDateKey].splice(taskIndex, 1); + + if (this.tasks[fromDateKey].length === 0) { + delete this.tasks[fromDateKey]; + } + + if (!this.tasks[toDateKey]) { + this.tasks[toDateKey] = []; + } + + this.tasks[toDateKey].push(task); + this.saveTasks(); + this.render(); + } + + getDateKey(date) { + return date.toISOString().split('T')[0]; + } + + isToday(date) { + const today = new Date(); + return date.toDateString() === today.toDateString(); + } + + saveTasks() { + localStorage.setItem('planningTasks', JSON.stringify(this.tasks)); + } + + loadTasks() { + const stored = localStorage.getItem('planningTasks'); + return stored ? JSON.parse(stored) : {}; + } + + resetAll() { + if (confirm('¿Estás seguro de que deseas limpiar todas las tareas?')) { + this.tasks = {}; + this.saveTasks(); + this.render(); + } + } + } + + // Inicializar cuando el DOM esté listo + document.addEventListener('DOMContentLoaded', () => { + new PlanningCalendar(); + }); + \ No newline at end of file