chartManD commited on
Commit
ea65589
·
1 Parent(s): 8d9f6d7

Se manejan las palabras existentes para nappgin con perfil ultra flash

Browse files
tecnicas/controllers/api_controller/rating_napping_controller.py CHANGED
@@ -1,7 +1,8 @@
1
  from django.http import JsonResponse
2
  from django.http import HttpRequest
3
  from django.db import transaction
4
- from tecnicas.models import Calificacion, DatoPunto, Producto, Participacion
 
5
 
6
 
7
  class RatingNappingController:
@@ -20,6 +21,10 @@ class RatingNappingController:
20
  participation.tecnica, participation.catador
21
  )
22
 
 
 
 
 
23
  new_ratings = []
24
  ids_products = products_map.keys()
25
  for item in data:
@@ -71,12 +76,75 @@ class RatingNappingController:
71
  if points_to_update:
72
  DatoPunto.objects.bulk_update(points_to_update, ['x', 'y'])
73
 
 
 
 
 
74
  return JsonResponse({"message": "Datos guardados exitosamente"})
75
 
76
  except Exception as e:
77
  print("ERROR:", e)
78
  return JsonResponse({"error": "Error al procesar datos"})
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  @staticmethod
81
  def getProductsMap(id_tecnica):
82
  products_qs = Producto.objects.filter(id_tecnica=id_tecnica)
 
1
  from django.http import JsonResponse
2
  from django.http import HttpRequest
3
  from django.db import transaction
4
+ from tecnicas.models import Calificacion, DatoPunto, Producto, Participacion, Palabra
5
+ from tecnicas.forms import ListWordsForm
6
 
7
 
8
  class RatingNappingController:
 
21
  participation.tecnica, participation.catador
22
  )
23
 
24
+ validation_result = RatingNappingController.validateWords(data)
25
+ if validation_result is not None:
26
+ return validation_result
27
+
28
  new_ratings = []
29
  ids_products = products_map.keys()
30
  for item in data:
 
76
  if points_to_update:
77
  DatoPunto.objects.bulk_update(points_to_update, ['x', 'y'])
78
 
79
+ RatingNappingController.processWordsForRatings(
80
+ data, existing_ratings_map
81
+ )
82
+
83
  return JsonResponse({"message": "Datos guardados exitosamente"})
84
 
85
  except Exception as e:
86
  print("ERROR:", e)
87
  return JsonResponse({"error": "Error al procesar datos"})
88
 
89
+ @staticmethod
90
+ def validateWords(data: list):
91
+ """Valida todas las palabras de todos los items usando ListWordsForm.
92
+ Retorna JsonResponse con error si hay problemas, None si todo está bien."""
93
+ for item in data:
94
+ words = item.get("words", [])
95
+ if words:
96
+ dic_words = {}
97
+ for index, word in enumerate(words, start=1):
98
+ dic_words[f"palabra_{index}"] = word
99
+
100
+ form = ListWordsForm(dic_words, new_words=words)
101
+ if not form.is_valid():
102
+ errors = []
103
+ for field, error_list in form.errors.items():
104
+ errors.extend(error_list)
105
+ return JsonResponse({"error": f"Error en validación de palabras: {', '.join(errors)}"})
106
+ return None
107
+
108
+ @staticmethod
109
+ def processWordsForRatings(data: list, existing_ratings_map: dict):
110
+ """Procesa y asocia palabras a las calificaciones.
111
+ Crea palabras que no existan y las asocia a las calificaciones correspondientes."""
112
+
113
+ all_words = set()
114
+ for item in data:
115
+ words = item.get("words", [])
116
+ if words:
117
+ all_words.update(words)
118
+
119
+ if not all_words:
120
+ return
121
+
122
+ existing_words = Palabra.objects.filter(
123
+ nombre_palabra__in=all_words
124
+ )
125
+ existing_words_map = {w.nombre_palabra: w for w in existing_words}
126
+
127
+ word_objects = {}
128
+ for word_name in all_words:
129
+ if word_name in existing_words_map:
130
+ word_objects[word_name] = existing_words_map[word_name]
131
+ else:
132
+ word_obj, created = Palabra.objects.get_or_create(
133
+ nombre_palabra=word_name
134
+ )
135
+ word_objects[word_name] = word_obj
136
+
137
+ for item in data:
138
+ words = item.get("words", [])
139
+ if words:
140
+ product_id = int(item["idProduct"])
141
+ rating = existing_ratings_map.get(product_id)
142
+
143
+ if rating:
144
+ words_to_set = [word_objects[word_name]
145
+ for word_name in words]
146
+ rating.palabras.set(words_to_set)
147
+
148
  @staticmethod
149
  def getProductsMap(id_tecnica):
150
  products_qs = Producto.objects.filter(id_tecnica=id_tecnica)
tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_napping_controller.py CHANGED
@@ -2,7 +2,7 @@ from django.http import HttpRequest
2
  from django.shortcuts import redirect, render
3
  from django.urls import reverse
4
  from django.db.models import F
5
- from tecnicas.models import Participacion, Producto, TecnicaModalidad, DatoPunto, Calificacion, Modalidad
6
  from tecnicas.forms import ListWordsForm
7
  from tecnicas.utils import noValidTechnique
8
  from .general_test_controller import GenetalTestController
@@ -71,6 +71,7 @@ class TestNappingController(GenetalTestController):
71
  products_in_technique = Producto.objects.filter(id_tecnica=technique)
72
  self.context["products"] = products_in_technique
73
  self.setCoordinates()
 
74
 
75
  return render(request, self.napping_puf_test, self.context)
76
 
@@ -93,3 +94,22 @@ class TestNappingController(GenetalTestController):
93
  )
94
 
95
  self.context["data_points"] = list(data_points)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  from django.shortcuts import redirect, render
3
  from django.urls import reverse
4
  from django.db.models import F
5
+ from tecnicas.models import Participacion, Producto, TecnicaModalidad, DatoPunto, Calificacion, Modalidad, Palabra
6
  from tecnicas.forms import ListWordsForm
7
  from tecnicas.utils import noValidTechnique
8
  from .general_test_controller import GenetalTestController
 
71
  products_in_technique = Producto.objects.filter(id_tecnica=technique)
72
  self.context["products"] = products_in_technique
73
  self.setCoordinates()
74
+ self.setWords()
75
 
76
  return render(request, self.napping_puf_test, self.context)
77
 
 
94
  )
95
 
96
  self.context["data_points"] = list(data_points)
97
+
98
+ def setWords(self):
99
+ technique = self.session.tecnica
100
+
101
+ ratings = Calificacion.objects.filter(
102
+ num_repeticion=0,
103
+ id_tecnica=technique,
104
+ id_catador=self.participation.catador
105
+ ).prefetch_related('palabras', 'id_producto')
106
+
107
+ words_by_product = {}
108
+ for rating in ratings:
109
+ product_code = rating.id_producto.codigoProducto
110
+ words_list = list(rating.palabras.values_list(
111
+ 'nombre_palabra', flat=True))
112
+ if words_list:
113
+ words_by_product[product_code] = words_list
114
+
115
+ self.context["words_by_product"] = words_by_product
tecnicas/static/js/test-napping-plane.js CHANGED
@@ -15,7 +15,6 @@ window.placedPoints = {};
15
  const modeElement = document.querySelector('[data-mode]');
16
  window.isUltraFlash = modeElement && modeElement.dataset.mode.toLowerCase().includes('ultra flash');
17
 
18
- // For normal mode (sin modalidad), placement is always active
19
  // For ultra flash mode, it starts active but will be controlled by ultra-flash.js
20
  if (window.isUltraFlash) {
21
  document.getElementById("question-save").classList.add("hidden");
@@ -42,7 +41,6 @@ products.forEach(product => {
42
 
43
  // 2. Handle Plane Click (Placing Points)
44
  planeContainer.addEventListener('click', (e) => {
45
- // If placement is not active, do nothing (or handle differently in other script)
46
  if (!window.isPlacementActive) return;
47
 
48
  if (!selectedProductCode) {
@@ -163,6 +161,12 @@ document
163
  ////
164
  */
165
 
 
 
 
 
 
 
166
  window.saveData = async function () {
167
  const codeProducts = Object.keys(window.placedPoints);
168
  const data = [];
@@ -172,6 +176,14 @@ window.saveData = async function () {
172
  return;
173
  }
174
 
 
 
 
 
 
 
 
 
175
  codeProducts.forEach((code) => {
176
  const point = window.placedPoints[code];
177
 
@@ -182,6 +194,12 @@ window.saveData = async function () {
182
  idProduct: point.id
183
  };
184
 
 
 
 
 
 
 
185
  data.push(objData);
186
  })
187
 
 
15
  const modeElement = document.querySelector('[data-mode]');
16
  window.isUltraFlash = modeElement && modeElement.dataset.mode.toLowerCase().includes('ultra flash');
17
 
 
18
  // For ultra flash mode, it starts active but will be controlled by ultra-flash.js
19
  if (window.isUltraFlash) {
20
  document.getElementById("question-save").classList.add("hidden");
 
41
 
42
  // 2. Handle Plane Click (Placing Points)
43
  planeContainer.addEventListener('click', (e) => {
 
44
  if (!window.isPlacementActive) return;
45
 
46
  if (!selectedProductCode) {
 
161
  ////
162
  */
163
 
164
+ // Callback for additional validation before saving (can be set by ultra flash)
165
+ window.beforeSaveData = null;
166
+
167
+ // Function to get extra data for each product (can be set by ultra flash)
168
+ window.getExtraDataForSave = null;
169
+
170
  window.saveData = async function () {
171
  const codeProducts = Object.keys(window.placedPoints);
172
  const data = [];
 
176
  return;
177
  }
178
 
179
+ // Call beforeSaveData callback if it exists (for ultra flash validation)
180
+ if (window.beforeSaveData && typeof window.beforeSaveData === 'function') {
181
+ const validationResult = window.beforeSaveData();
182
+ if (validationResult === false) {
183
+ return false;
184
+ }
185
+ }
186
+
187
  codeProducts.forEach((code) => {
188
  const point = window.placedPoints[code];
189
 
 
194
  idProduct: point.id
195
  };
196
 
197
+ // Get extra data if callback exists (for ultra flash words)
198
+ if (window.getExtraDataForSave && typeof window.getExtraDataForSave === 'function') {
199
+ const extraData = window.getExtraDataForSave(code);
200
+ Object.assign(objData, extraData);
201
+ }
202
+
203
  data.push(objData);
204
  })
205
 
tecnicas/static/js/test-napping-ultra-flash.js CHANGED
@@ -3,16 +3,15 @@ const productWords = {};
3
 
4
  // Only initialize ultra flash if the mode is active
5
  if (window.isUltraFlash) {
6
- initUltraFlash(productWords);
7
  }
8
 
9
- function initUltraFlash(productWords) {
10
  const continueBtn = document.getElementById('continue-description');
11
  const questionSaveBtn = document.getElementById('question-save');
12
- const saveProgressBtn = document.getElementById('save-progress');
13
  const dialog = document.getElementById('word-dialog');
14
  const wordForm = document.getElementById('word-form');
15
- const wordInput = document.getElementById('word-input');
16
  const wordList = document.getElementById('word-list');
17
  const dialogProductCode = document.getElementById('dialog-product-code');
18
 
@@ -22,6 +21,8 @@ function initUltraFlash(productWords) {
22
  const points = document.querySelectorAll('.data-point');
23
  let hasExistingWords = false;
24
 
 
 
25
  // Check if there are existing words from backend
26
  points.forEach(point => {
27
  const code = point.dataset.code;
@@ -29,26 +30,28 @@ function initUltraFlash(productWords) {
29
 
30
  if (wordsAttr && wordsAttr.trim() !== '') {
31
  productWords[code] = wordsAttr.split(',').filter(w => w.trim() !== '');
32
- if (productWords[code].length > 0) {
 
33
  hasExistingWords = true;
34
  }
35
  }
36
  });
37
 
38
- // If words already exist, skip phase 1 and go directly to description phase
39
- if (hasExistingWords) {
40
- startDescriptionPhase();
41
- // Update all point labels to show existing words
42
- points.forEach(point => {
43
- const code = point.dataset.code;
44
- if (productWords[code] && productWords[code].length > 0) {
45
- updatePointLabel(code);
46
- }
47
- });
48
- } else {
49
- // No existing words, show continue button for phase 1
50
- continueBtn.classList.remove('hidden');
51
- }
 
52
 
53
  // Check if all products are placed
54
  continueBtn.addEventListener('click', () => {
@@ -65,21 +68,14 @@ function initUltraFlash(productWords) {
65
 
66
  function startDescriptionPhase() {
67
  isDescriptionPhase = true;
68
- window.isPlacementActive = false; // Freeze point placement
69
-
70
- // Hide continue button and show finish options
71
  continueBtn.classList.add('hidden');
72
  questionSaveBtn.classList.remove("hidden");
73
-
74
- // Update UI to indicate description phase
75
  spanNotifaction("Fase de descripción: Haz clic en un punto para agregar palabras.", false);
76
 
77
- // Change cursor style to indicate different mode
78
  const plane = document.getElementById('napping-plane');
79
  plane.classList.remove('cursor-crosshair');
80
  plane.classList.add('cursor-default');
81
-
82
- // Remove any product selection highlights
83
  document.querySelectorAll('.item-product').forEach(p => {
84
  p.classList.remove('ring-4', 'ring-primary');
85
  });
@@ -170,9 +166,10 @@ function initUltraFlash(productWords) {
170
  if (!point) return;
171
 
172
  const words = productWords[code] || [];
173
-
174
  // Remove existing tooltip if present
175
- let tooltip = point.querySelector('.group-hover\\:block');
 
176
  if (tooltip) {
177
  tooltip.remove();
178
  }
@@ -180,8 +177,8 @@ function initUltraFlash(productWords) {
180
  // Only create tooltip in description phase and if there are words
181
  if (isDescriptionPhase && words.length > 0) {
182
  tooltip = document.createElement('div');
183
- tooltip.className = 'absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-2 bg-gray-800 text-white text-xs rounded z-10 hidden group-hover:block';
184
-
185
  // Display words in a 3-column grid (no coordinates)
186
  const wordBadges = words.map(w => `<span class="inline-block px-2 py-1 bg-yellow-600 text-white rounded text-xs">${w}</span>`).join('');
187
  tooltip.innerHTML = `
@@ -190,29 +187,20 @@ function initUltraFlash(productWords) {
190
  ${wordBadges}
191
  </div>
192
  `;
193
-
194
  // Add max-width to tooltip
195
  tooltip.style.maxWidth = '320px';
196
  tooltip.style.whiteSpace = 'normal';
197
-
198
  point.appendChild(tooltip);
199
  }
200
  }
201
 
202
- // Override saveData to include words for ultra flash mode
203
- // This replaces the basic saveData from test-napping-plane.js
204
- window.saveData = async function () {
205
- const codeProducts = Object.keys(window.placedPoints);
206
- const totalProducts = document.querySelectorAll('.item-product').length;
207
-
208
- // Validate all products are placed
209
- if (totalProducts != codeProducts.length) {
210
- spanNotifaction("Por favor, coloca todos los puntos");
211
- return false;
212
- }
213
-
214
  // If in description phase, validate words (minimum 1 per product)
215
  if (isDescriptionPhase) {
 
216
  for (const code of codeProducts) {
217
  const words = productWords[code] || [];
218
  if (words.length < 1) {
@@ -221,55 +209,15 @@ function initUltraFlash(productWords) {
221
  }
222
  }
223
  }
 
 
224
 
225
- // Prepare data with coordinates and words
226
- const data = [];
227
- codeProducts.forEach((code) => {
228
- const point = window.placedPoints[code];
229
- const words = productWords[code] || [];
230
-
231
- const objData = {
232
- code: code,
233
- x: point.x,
234
- y: point.y,
235
- idProduct: point.id,
236
- words: words // Include words for ultra flash mode
237
- };
238
-
239
- data.push(objData);
240
- });
241
-
242
- // Send data to server
243
- const URL = "/cata/testers/api/rating-napping/no-mode";
244
- const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
245
-
246
- try {
247
- const response = await fetch(URL, {
248
- method: "POST",
249
- headers: {
250
- "Content-Type": "application/json",
251
- "X-CSRFToken": csrfToken,
252
- },
253
- body: JSON.stringify(data),
254
- });
255
-
256
- if (!response.ok) {
257
- spanNotifaction("Error en la respuesta del servidor");
258
- return false;
259
- }
260
-
261
- const result = await response.json();
262
-
263
- if (result.error) {
264
- spanNotifaction(result.error);
265
- return false;
266
- } else {
267
- spanNotifaction(result.message, false);
268
- return true;
269
- }
270
- } catch (error) {
271
- spanNotifaction("Error en proceso de guardar los datos");
272
- return false;
273
- }
274
- }
275
  }
 
3
 
4
  // Only initialize ultra flash if the mode is active
5
  if (window.isUltraFlash) {
6
+ initUltraFlash();
7
  }
8
 
9
+ function initUltraFlash() {
10
  const continueBtn = document.getElementById('continue-description');
11
  const questionSaveBtn = document.getElementById('question-save');
 
12
  const dialog = document.getElementById('word-dialog');
13
  const wordForm = document.getElementById('word-form');
14
+ const wordInput = document.querySelector('.cts-input-list-word');
15
  const wordList = document.getElementById('word-list');
16
  const dialogProductCode = document.getElementById('dialog-product-code');
17
 
 
21
  const points = document.querySelectorAll('.data-point');
22
  let hasExistingWords = false;
23
 
24
+ wordInput.value = '';
25
+
26
  // Check if there are existing words from backend
27
  points.forEach(point => {
28
  const code = point.dataset.code;
 
30
 
31
  if (wordsAttr && wordsAttr.trim() !== '') {
32
  productWords[code] = wordsAttr.split(',').filter(w => w.trim() !== '');
33
+
34
+ if (productWords[code].length >= 1) {
35
  hasExistingWords = true;
36
  }
37
  }
38
  });
39
 
40
+ setTimeout(() => {
41
+ if (hasExistingWords) {
42
+ startDescriptionPhase();
43
+ // Update all point labels to show existing words
44
+ points.forEach(point => {
45
+ const code = point.dataset.code;
46
+ if (productWords[code] && productWords[code].length > 0) {
47
+ updatePointLabel(code);
48
+ }
49
+ });
50
+ } else {
51
+ // No existing words, show continue button for phase 1
52
+ continueBtn.classList.remove('hidden');
53
+ }
54
+ }, 100);
55
 
56
  // Check if all products are placed
57
  continueBtn.addEventListener('click', () => {
 
68
 
69
  function startDescriptionPhase() {
70
  isDescriptionPhase = true;
71
+ window.isPlacementActive = false;
 
 
72
  continueBtn.classList.add('hidden');
73
  questionSaveBtn.classList.remove("hidden");
 
 
74
  spanNotifaction("Fase de descripción: Haz clic en un punto para agregar palabras.", false);
75
 
 
76
  const plane = document.getElementById('napping-plane');
77
  plane.classList.remove('cursor-crosshair');
78
  plane.classList.add('cursor-default');
 
 
79
  document.querySelectorAll('.item-product').forEach(p => {
80
  p.classList.remove('ring-4', 'ring-primary');
81
  });
 
166
  if (!point) return;
167
 
168
  const words = productWords[code] || [];
169
+
170
  // Remove existing tooltip if present
171
+ let tooltip = point.querySelector('.cts-tooltip');
172
+
173
  if (tooltip) {
174
  tooltip.remove();
175
  }
 
177
  // Only create tooltip in description phase and if there are words
178
  if (isDescriptionPhase && words.length > 0) {
179
  tooltip = document.createElement('div');
180
+ tooltip.className = 'cts-tooltip absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-2 bg-gray-800 text-white text-xs rounded z-10 opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none';
181
+
182
  // Display words in a 3-column grid (no coordinates)
183
  const wordBadges = words.map(w => `<span class="inline-block px-2 py-1 bg-yellow-600 text-white rounded text-xs">${w}</span>`).join('');
184
  tooltip.innerHTML = `
 
187
  ${wordBadges}
188
  </div>
189
  `;
190
+
191
  // Add max-width to tooltip
192
  tooltip.style.maxWidth = '320px';
193
  tooltip.style.whiteSpace = 'normal';
 
194
  point.appendChild(tooltip);
195
  }
196
  }
197
 
198
+ // Set up callbacks to extend the base saveData function
199
+ // Validation callback - runs before saving
200
+ window.beforeSaveData = function () {
 
 
 
 
 
 
 
 
 
201
  // If in description phase, validate words (minimum 1 per product)
202
  if (isDescriptionPhase) {
203
+ const codeProducts = Object.keys(window.placedPoints);
204
  for (const code of codeProducts) {
205
  const words = productWords[code] || [];
206
  if (words.length < 1) {
 
209
  }
210
  }
211
  }
212
+ return true;
213
+ };
214
 
215
+ // Data extension callback - adds words to each product's data
216
+ window.getExtraDataForSave = function (code) {
217
+ const words = productWords[code] || [];
218
+ console.log(`Getting words for ${code}:`, words);
219
+ return {
220
+ words: words
221
+ };
222
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  }
tecnicas/templates/tecnicas/components/dialog-nap-puf.html CHANGED
@@ -1,14 +1,17 @@
1
  <dialog id="word-dialog" class="modal">
2
- <div class="modal-box bg-surface-ligt">
3
  <h3 class="font-bold text-lg">
4
  Describir producto
5
  <span id="dialog-product-code"></span>
6
  </h3>
7
- <p class="py-4">Agrega palabras que describan este producto (máximo 5).</p>
8
- <form id="word-form" class="flex gap-2 mb-4">
9
- <input type="text" id="word-input" placeholder="Palabra..."
10
- class="input input-bordered bg-surface-light w-full text-black" autocomplete="off">
11
- <button type="submit" class="btn btn-primary">Agregar</button>
 
 
 
12
  </form>
13
 
14
  <div id="word-list" class="flex flex-wrap gap-2 mb-4 min-h-[50px]"></div>
 
1
  <dialog id="word-dialog" class="modal">
2
+ <div class="modal-box bg-surface-ligt space-y-4">
3
  <h3 class="font-bold text-lg">
4
  Describir producto
5
  <span id="dialog-product-code"></span>
6
  </h3>
7
+
8
+ <form id="word-form" class="cts-form-pf-word flex justify-center items-center gap-4">
9
+ <label for="{{ form.nombre_palabra.id_for_label }}" class="text-left flex-1">
10
+ {{ form.nombre_palabra }}
11
+ </label>
12
+ <button type="submit" class="cts-btn-general-compress cts-btn-primary btn-push py-1 px-4">
13
+ Agregar
14
+ </button>
15
  </form>
16
 
17
  <div id="word-list" class="flex flex-wrap gap-2 mb-4 min-h-[50px]"></div>
tecnicas/templates/tecnicas/forms_tester/test_napping_puf.html CHANGED
@@ -1,6 +1,6 @@
1
  {% extends 'tecnicas/layouts/base.html' %}
2
-
3
  {% load static %}
 
4
 
5
  {% block title %}Napping{% endblock %}
6
 
@@ -34,12 +34,6 @@
34
  {% endif %}
35
 
36
  <article class="rounded flex flex-col gap-4">
37
- <section class="flex items-center justify-center bg-surface-ligt p-2 rounded-lg">
38
- <p class="text-lg font-medium text-center">
39
- {{ session.tecnica.instrucciones }}
40
- </p>
41
- </section>
42
-
43
  <section class="flex items-center justify-center flex-wrap gap-4 bg-surface-ligt p-2 rounded-lg">
44
  <p class="text-xl font-bold text-center capitalize" data-mode="{{ mode }}">
45
  Modalidad: {% if mode != "sin modalidad" %}{{ mode }} {% else %} Nappging {% endif %}
@@ -48,7 +42,15 @@
48
  </article>
49
 
50
  <article class="container-rating-word p-2 py-6 space-y-6 bg-surface-ligt rounded min-w-3xl">
51
- <h2 class="text-xl font-bold">Productos en la sesión</h2>
 
 
 
 
 
 
 
 
52
  <div id="items" class="original-products flex gap-4 flex-wrap justify-center"
53
  data-original-products="original">
54
  {% for product in products %}
@@ -73,13 +75,11 @@
73
  <div id="point-{{ point.code }}"
74
  class="data-point absolute w-4 h-4 bg-red-600 rounded-full transform -translate-x-1/2 -translate-y-1/2 cursor-pointer border-2 border-white shadow-md group"
75
  data-px="{{ point.px }}" data-py="{{ point.py }}" data-code="{{ point.code }}"
76
- data-id-product="{{ point.id_product }}" {% if point.words %}
77
- data-words="{{ point.words|join:',' }}" {% endif %}>
78
  <div
79
  class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-gray-800 text-white text-xs rounded whitespace-nowrap z-10 hidden group-hover:block">
80
- <strong>{{ point.code }}</strong><br>
81
- X: {{ point.px }}<br>
82
- Y: {{ point.py }}
83
  </div>
84
  <span
85
  class="absolute top-4 left-1/2 transform -translate-x-1/2 text-xs font-bold text-gray-700 pointer-events-none">
@@ -102,6 +102,20 @@
102
  </span>
103
  </section>
104
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  <section role="alert" class="alert alert-warning">
106
  <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none"
107
  viewBox="0 0 24 24">
@@ -124,11 +138,9 @@
124
  <button id="save-progress" class="cts-btn-general cts-btn-primary btn-push">
125
  Guardar progreso
126
  </button>
127
- {% if mode == "perfil ultra flash" %}
128
  <button id="continue-description" class="cts-btn-general cts-btn-primary btn-push hidden">
129
  Continuar a descripción
130
  </button>
131
- {% endif %}
132
  <div class="flex gap-2">
133
  <button id="question-save" class="cts-btn-general cts-btn-secondary btn-push flex-1">
134
  ¿Finalizar sesión?
@@ -145,9 +157,7 @@
145
  </section>
146
  </article>
147
 
148
- {% if mode == "perfil ultra flash" %}
149
  {% include "../components/dialog-nap-puf.html" %}
150
- {% endif %}
151
 
152
  {% include "../components/toast-container.html" %}
153
  </article>
@@ -157,7 +167,5 @@
157
  {% block extra_js %}
158
  <script src="{% static 'js/actions-form.js' %}"></script>
159
  <script src="{% static 'js/test-napping-plane.js' %}"></script>
160
- {% if mode == "perfil ultra flash" %}
161
  <script src="{% static 'js/test-napping-ultra-flash.js' %}"></script>
162
- {% endif %}
163
  {% endblock %}
 
1
  {% extends 'tecnicas/layouts/base.html' %}
 
2
  {% load static %}
3
+ {% load custom_filters %}
4
 
5
  {% block title %}Napping{% endblock %}
6
 
 
34
  {% endif %}
35
 
36
  <article class="rounded flex flex-col gap-4">
 
 
 
 
 
 
37
  <section class="flex items-center justify-center flex-wrap gap-4 bg-surface-ligt p-2 rounded-lg">
38
  <p class="text-xl font-bold text-center capitalize" data-mode="{{ mode }}">
39
  Modalidad: {% if mode != "sin modalidad" %}{{ mode }} {% else %} Nappging {% endif %}
 
42
  </article>
43
 
44
  <article class="container-rating-word p-2 py-6 space-y-6 bg-surface-ligt rounded min-w-3xl">
45
+ <section class="flex items-center justify-around flex-wrap gap-4 bg-surface-ligt p-2 rounded-lg">
46
+ <h2 class="text-xl font-bold">Productos en la sesión</h2>
47
+ <div class="flex flex-col items-center justify-center flex-wrap">
48
+ <p class="text-lg font-bold text-center">Instrucciones</p>
49
+ <p class="text-lg font-normal text-center">
50
+ {{ session.tecnica.instrucciones }}
51
+ </p>
52
+ </div>
53
+ </section>
54
  <div id="items" class="original-products flex gap-4 flex-wrap justify-center"
55
  data-original-products="original">
56
  {% for product in products %}
 
75
  <div id="point-{{ point.code }}"
76
  class="data-point absolute w-4 h-4 bg-red-600 rounded-full transform -translate-x-1/2 -translate-y-1/2 cursor-pointer border-2 border-white shadow-md group"
77
  data-px="{{ point.px }}" data-py="{{ point.py }}" data-code="{{ point.code }}"
78
+ data-id-product="{{ point.id_product }}" {% if words_by_product|get_item:point.code %}
79
+ data-words="{{ words_by_product|get_item:point.code|join:',' }}" {% endif %}>
80
  <div
81
  class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-gray-800 text-white text-xs rounded whitespace-nowrap z-10 hidden group-hover:block">
82
+ <strong>{{ point.code }}</strong>
 
 
83
  </div>
84
  <span
85
  class="absolute top-4 left-1/2 transform -translate-x-1/2 text-xs font-bold text-gray-700 pointer-events-none">
 
102
  </span>
103
  </section>
104
 
105
+ <section role="alert" class="alert alert-warning">
106
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none"
107
+ viewBox="0 0 24 24">
108
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
109
+ d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
110
+ </svg>
111
+ <div class="flex-col">
112
+ <span class="text-lg block">
113
+ Si ya has agregado <b>atributos</b> a un punto y <b>guardas el progreso</b> ya no será posible
114
+ volver a mover los puntos
115
+ </span>
116
+ </div>
117
+ </section>
118
+
119
  <section role="alert" class="alert alert-warning">
120
  <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none"
121
  viewBox="0 0 24 24">
 
138
  <button id="save-progress" class="cts-btn-general cts-btn-primary btn-push">
139
  Guardar progreso
140
  </button>
 
141
  <button id="continue-description" class="cts-btn-general cts-btn-primary btn-push hidden">
142
  Continuar a descripción
143
  </button>
 
144
  <div class="flex gap-2">
145
  <button id="question-save" class="cts-btn-general cts-btn-secondary btn-push flex-1">
146
  ¿Finalizar sesión?
 
157
  </section>
158
  </article>
159
 
 
160
  {% include "../components/dialog-nap-puf.html" %}
 
161
 
162
  {% include "../components/toast-container.html" %}
163
  </article>
 
167
  {% block extra_js %}
168
  <script src="{% static 'js/actions-form.js' %}"></script>
169
  <script src="{% static 'js/test-napping-plane.js' %}"></script>
 
170
  <script src="{% static 'js/test-napping-ultra-flash.js' %}"></script>
 
171
  {% endblock %}