Norberto Montalvo García commited on
Commit
b9d9c94
·
unverified ·
2 Parent(s): a067a65 a0fd4e3

Merge pull request #36 from CascoArcilla/HU10

Browse files
Files changed (47) hide show
  1. tecnicas/admin.py +2 -1
  2. tecnicas/controllers/__init__.py +12 -4
  3. tecnicas/controllers/api_controller/rating_pf_list_controller.py +143 -0
  4. tecnicas/controllers/models_controller/dato_controller.py +40 -14
  5. tecnicas/controllers/views_controller/session_management/details_pf_controller.py +155 -2
  6. tecnicas/controllers/views_controller/session_management/monitor_escalas_controller.py +0 -3
  7. tecnicas/controllers/views_controller/session_management/monitor_pf_controller.py +75 -0
  8. tecnicas/controllers/views_controller/session_management/monitor_rata_controller.py +0 -3
  9. tecnicas/controllers/views_controller/sessions_tester/{init_session_tester_controller.py → init_session/init_session_controller.py} +5 -85
  10. tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_escalas_controller.py +82 -0
  11. tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_pf_controller.py +144 -0
  12. tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_rata_controller.py +24 -0
  13. tecnicas/controllers/views_controller/sessions_tester/login_session_tester_controller.py +4 -1
  14. tecnicas/controllers/views_controller/sessions_tester/{general_test_controller.py → tests_forms/general_test_controller.py} +0 -0
  15. tecnicas/controllers/views_controller/sessions_tester/{test_cata_controller.py → tests_forms/test_cata_controller.py} +0 -0
  16. tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_pf_controller.py +188 -0
  17. tecnicas/controllers/views_controller/sessions_tester/{test_rata_controller.py → tests_forms/test_rata_controller.py} +0 -0
  18. tecnicas/controllers/views_controller/sessions_tester/{test_scales_controller.py → tests_forms/test_scales_controller.py} +6 -1
  19. tecnicas/forms/__init__.py +2 -0
  20. tecnicas/forms/list_words_form.py +49 -0
  21. tecnicas/migrations/0022_alter_sesionsensorial_codigo_sesion.py +19 -0
  22. tecnicas/migrations/0023_alter_sesionsensorial_codigo_sesion_listapalabras.py +30 -0
  23. tecnicas/models/__init__.py +3 -1
  24. tecnicas/models/lista_palabras.py +15 -0
  25. tecnicas/static/js/download-table-zip.js +74 -0
  26. tecnicas/static/js/lists-words-testers.js +103 -0
  27. tecnicas/static/js/pf-make-list.js +227 -0
  28. tecnicas/static/js/test-cata.js +0 -2
  29. tecnicas/templates/tecnicas/components/form-scale-continue.html +3 -2
  30. tecnicas/templates/tecnicas/components/item-list-words.html +8 -0
  31. tecnicas/templates/tecnicas/components/table_pf.html +39 -0
  32. tecnicas/templates/tecnicas/forms_tester/convencional.html +1 -1
  33. tecnicas/templates/tecnicas/forms_tester/init_session_pf.html +123 -0
  34. tecnicas/templates/tecnicas/forms_tester/test_pf_list_words.html +184 -0
  35. tecnicas/templates/tecnicas/forms_tester/test_pf_rating_list.html +129 -0
  36. tecnicas/templates/tecnicas/manage_sesions/details-session-pf.html +68 -14
  37. tecnicas/templates/tecnicas/manage_sesions/monitor-session-pf.html +126 -0
  38. tecnicas/urls.py +8 -0
  39. tecnicas/views/__init__.py +2 -0
  40. tecnicas/views/apis/api_list_words_pf.py +23 -0
  41. tecnicas/views/apis/rating_word_cata.py +0 -2
  42. tecnicas/views/sessions_management/session_details.py +7 -2
  43. tecnicas/views/sessions_management/session_monitor.py +19 -2
  44. tecnicas/views/tester_forms/cata_test.py +0 -1
  45. tecnicas/views/tester_forms/init_tester_form.py +23 -9
  46. tecnicas/views/tester_forms/login_session.py +4 -0
  47. tecnicas/views/tester_forms/pf_test.py +19 -0
tecnicas/admin.py CHANGED
@@ -14,7 +14,7 @@ from .models import Producto, Participacion
14
 
15
  from .models import Orden, Posicion
16
 
17
- from .models import Dato, ValorDecimal, ValorBooleano, Calificacion
18
 
19
  # Register your models here.
20
  admin.site.register(CategoriaTecnica)
@@ -46,3 +46,4 @@ admin.site.register(Dato)
46
  admin.site.register(ValorDecimal)
47
  admin.site.register(ValorBooleano)
48
  admin.site.register(Calificacion)
 
 
14
 
15
  from .models import Orden, Posicion
16
 
17
+ from .models import Dato, ValorDecimal, ValorBooleano, Calificacion, ListaPalabras
18
 
19
  # Register your models here.
20
  admin.site.register(CategoriaTecnica)
 
46
  admin.site.register(ValorDecimal)
47
  admin.site.register(ValorBooleano)
48
  admin.site.register(Calificacion)
49
+ admin.site.register(ListaPalabras)
tecnicas/controllers/__init__.py CHANGED
@@ -23,20 +23,28 @@ from .views_controller.session_management.details_escala_controller import Detal
23
  from .views_controller.session_management.details_rata_controller import DetallesRATAController
24
  from .views_controller.session_management.details_cata_controller import DetallesCATAController
25
  from .views_controller.session_management.details_pf_controller import DetallesPFController
 
26
  from .views_controller.session_management.monitor_controller import MonitorController
27
  from .views_controller.session_management.monitor_escalas_controller import MonitorEscalasController
28
  from .views_controller.session_management.monitor_rata_controller import MonitorRATAController
 
29
 
30
  from .views_controller.sessions_tester.login_session_tester_controller import LoginSessionTesterController
31
- from .views_controller.sessions_tester.init_session_tester_controller import InitSessionTesterController
32
  from .views_controller.sessions_tester.list_sessions_tester_controller import ListSessionsTesterController
33
- from .views_controller.sessions_tester.test_scales_controller import TestScalesController
34
- from .views_controller.sessions_tester.test_rata_controller import TestRataController
35
- from .views_controller.sessions_tester.test_cata_controller import TestCataController
 
 
 
 
 
 
36
 
37
  from .views_controller.vocabulary_manage.create_vocabulary_controller import CreateVocabularyController
38
  from .views_controller.vocabulary_manage.list_vocabulary_controller import ListVocabularyController
39
 
40
  from .api_controller.rating_sacales_controller import RatingScalesController
41
  from .api_controller.rating_cata_controller import RatingCataController
 
42
  from .views_controller.tester_list_controller import TesterListController
 
23
  from .views_controller.session_management.details_rata_controller import DetallesRATAController
24
  from .views_controller.session_management.details_cata_controller import DetallesCATAController
25
  from .views_controller.session_management.details_pf_controller import DetallesPFController
26
+
27
  from .views_controller.session_management.monitor_controller import MonitorController
28
  from .views_controller.session_management.monitor_escalas_controller import MonitorEscalasController
29
  from .views_controller.session_management.monitor_rata_controller import MonitorRATAController
30
+ from .views_controller.session_management.monitor_pf_controller import MonitorPFController
31
 
32
  from .views_controller.sessions_tester.login_session_tester_controller import LoginSessionTesterController
 
33
  from .views_controller.sessions_tester.list_sessions_tester_controller import ListSessionsTesterController
34
+
35
+ from .views_controller.sessions_tester.tests_forms.test_scales_controller import TestScalesController
36
+ from .views_controller.sessions_tester.tests_forms.test_rata_controller import TestRataController
37
+ from .views_controller.sessions_tester.tests_forms.test_cata_controller import TestCataController
38
+ from .views_controller.sessions_tester.tests_forms.test_pf_controller import TestPFController
39
+
40
+ from .views_controller.sessions_tester.init_session.init_session_escalas_controller import InitSessionEscalasController
41
+ from .views_controller.sessions_tester.init_session.init_session_rata_controller import InitSessionRATAController
42
+ from .views_controller.sessions_tester.init_session.init_session_pf_controller import InitSessionPFController
43
 
44
  from .views_controller.vocabulary_manage.create_vocabulary_controller import CreateVocabularyController
45
  from .views_controller.vocabulary_manage.list_vocabulary_controller import ListVocabularyController
46
 
47
  from .api_controller.rating_sacales_controller import RatingScalesController
48
  from .api_controller.rating_cata_controller import RatingCataController
49
+ from .api_controller.rating_pf_list_controller import RatingPFListController
50
  from .views_controller.tester_list_controller import TesterListController
tecnicas/controllers/api_controller/rating_pf_list_controller.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.http import JsonResponse, HttpRequest
2
+ from django.db import transaction, IntegrityError
3
+ from tecnicas.models import Palabra, ListaPalabras, Participacion
4
+ from tecnicas.forms import ListWordsForm
5
+
6
+
7
+ class RatingPFListController():
8
+ def __init__(self):
9
+ pass
10
+
11
+ @staticmethod
12
+ def getListWords(request: HttpRequest):
13
+ participation_current_tester = Participacion.objects.get(
14
+ id=request.session["id_participation"])
15
+
16
+ technique = participation_current_tester.tecnica
17
+
18
+ participations_testers = Participacion.objects.exclude(
19
+ catador=participation_current_tester.catador).filter(tecnica=technique)
20
+
21
+ all_testers = [
22
+ participation.catador for participation in participations_testers]
23
+
24
+ list_words_testers = list(ListaPalabras.objects.filter(
25
+ tecnica=technique, catador__in=all_testers, es_final=True))
26
+
27
+ if not list_words_testers:
28
+ return JsonResponse({"error": "Aun no hay listas finales de Catadores"})
29
+
30
+ result = []
31
+ for list_tester in list_words_testers:
32
+ try:
33
+ username = list_tester.catador.user.username
34
+ except Exception:
35
+ username = None
36
+
37
+ finish = [
38
+ participation.finalizado for participation in participations_testers if participation.catador.user.username == username][0]
39
+
40
+ status = "Lista terminada" if finish else "Lista en proceso"
41
+
42
+ words_qs = list_tester.palabras.all()
43
+ words = []
44
+ for p in words_qs:
45
+ nombre = getattr(p, 'nombre_palabra', None)
46
+ words.append({
47
+ 'id': getattr(p, 'id', None),
48
+ 'nombre_palabra': nombre
49
+ })
50
+
51
+ result.append({
52
+ 'username': username,
53
+ 'words': words,
54
+ 'status': status
55
+ })
56
+
57
+ return JsonResponse({
58
+ "message": "Listas encontradas",
59
+ "lists_words": result
60
+ })
61
+
62
+ @staticmethod
63
+ def saveList(request: HttpRequest, words: list, current_phase: int):
64
+ dic_words = {}
65
+ for index, word in enumerate(words, start=1):
66
+ dic_words[f"palabra_{index}"] = word
67
+
68
+ form = ListWordsForm(dic_words, new_words=words)
69
+
70
+ if form.is_valid():
71
+ participation = Participacion.objects.get(
72
+ id=request.session["id_participation"])
73
+
74
+ if not participation:
75
+ return JsonResponse({"error": "No está autorizado en la sesión"})
76
+
77
+ technique = participation.tecnica
78
+
79
+ list_words_tester: ListaPalabras
80
+ if current_phase == 1:
81
+ (list_words_tester, created) = ListaPalabras.objects.get_or_create(
82
+ tecnica=technique,
83
+ catador=request.user.user_catador,
84
+ es_final=False,
85
+ )
86
+ elif current_phase == 2:
87
+ (list_words_tester, created) = ListaPalabras.objects.get_or_create(
88
+ tecnica=technique,
89
+ catador=request.user.user_catador,
90
+ es_final=True,
91
+ )
92
+
93
+ added_words = RatingPFListController.addWordsToListWordsTester(
94
+ list_words=words, list_tester=list_words_tester)
95
+
96
+ response = JsonResponse({
97
+ "message": "Palabras guardadas con exito",
98
+ "words": [word.nombre_palabra for word in added_words]
99
+ })
100
+ else:
101
+ response = JsonResponse({"error": "Palabras invalidas"})
102
+
103
+ return response
104
+
105
+ @staticmethod
106
+ def addWordsToListWordsTester(list_words: list[str], list_tester: ListaPalabras):
107
+ # Normalizar
108
+ clean_words = [s.strip() for s in list_words if s.strip()]
109
+
110
+ # Obtener existentes
111
+ all_words = Palabra.objects.filter(nombre_palabra__in=clean_words)
112
+
113
+ names_words_exist = set(
114
+ all_words.values_list('nombre_palabra', flat=True))
115
+
116
+ # Ejecutar el query para no sumar palabras repetidas
117
+ all_words = list(all_words)
118
+
119
+ # Determinar faltantes
120
+ missing_words = [
121
+ nombre for nombre in clean_words if nombre not in names_words_exist]
122
+ print("No save words", missing_words)
123
+
124
+ created_words = []
125
+
126
+ # Intentar crear missing_words
127
+ for nombre in missing_words:
128
+ try:
129
+ with transaction.atomic():
130
+ palabra, created = Palabra.objects.get_or_create(
131
+ nombre_palabra=nombre)
132
+ if created:
133
+ created_words.append(palabra)
134
+ except IntegrityError:
135
+ palabra = Palabra.objects.get(nombre_palabra=nombre)
136
+ created_words.append(palabra)
137
+
138
+ # Combinar todas (all_words + created_words)
139
+ all_new_words = all_words + created_words
140
+
141
+ list_tester.palabras.set(all_new_words)
142
+
143
+ return all_new_words
tecnicas/controllers/models_controller/dato_controller.py CHANGED
@@ -1,5 +1,5 @@
1
- from ...models import Calificacion, Dato, Palabra, ValorDecimal, ValorBooleano, Tecnica
2
- from ...utils import controller_error, getId
3
  from django.core.exceptions import ValidationError
4
  from django.db.models import F
5
 
@@ -12,11 +12,7 @@ class DatoController():
12
  }
13
 
14
  self.data = Dato(**atributes)
15
-
16
- if isinstance(value_rating, bool):
17
- self.value_data = ValorBooleano(valor=value_rating)
18
- else:
19
- self.value_data = ValorDecimal(valor=value_rating)
20
 
21
  def setRating(self, new_rating: Calificacion):
22
  try:
@@ -39,15 +35,27 @@ class DatoController():
39
  except ValidationError as e:
40
  return controller_error(e.message)
41
 
42
- def setValue(self, new_value=None):
43
- if new_value:
44
- if isinstance(new_value, bool):
45
- self.value_data = ValorBooleano(valor=new_value)
46
- else:
47
- self.value_data = ValorDecimal(valor=new_value)
48
  else:
49
- self.value_data.id_dato = self.data
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
 
51
  return self.value_data
52
 
53
  def saveValue(self):
@@ -94,3 +102,21 @@ class DatoController():
94
  )
95
 
96
  return list(result)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from tecnicas.models import Calificacion, Dato, Palabra, ValorDecimal, ValorBooleano, Tecnica, Catador
2
+ from tecnicas.utils import controller_error, getId
3
  from django.core.exceptions import ValidationError
4
  from django.db.models import F
5
 
 
12
  }
13
 
14
  self.data = Dato(**atributes)
15
+ self.value_rating = value_rating
 
 
 
 
16
 
17
  def setRating(self, new_rating: Calificacion):
18
  try:
 
35
  except ValidationError as e:
36
  return controller_error(e.message)
37
 
38
+ def setValue(self):
39
+ if isinstance(self.value_rating, bool):
40
+ self.value_data = ValorBooleano(valor=self.value_rating)
41
+
 
 
42
  else:
43
+ type_scale = self.data.id_calificacion.id_tecnica.escala_tecnica.id_tipo_escala.nombre_escala
44
+
45
+
46
+ if type_scale == "continua":
47
+ decimal_value = self.value_rating/100
48
+ value_rounded = round(decimal_value)
49
+ self.value_data = ValorDecimal(valor=value_rounded)
50
+
51
+ print(self.value_rating)
52
+ print(decimal_value)
53
+ print(value_rounded)
54
+
55
+ else:
56
+ self.value_data = ValorDecimal(valor=self.value_rating)
57
 
58
+ self.value_data.id_dato = self.data
59
  return self.value_data
60
 
61
  def saveValue(self):
 
102
  )
103
 
104
  return list(result)
105
+
106
+ @staticmethod
107
+ def getWordValuesPF(technique: Tecnica, ratings: list[Calificacion], tester: Catador):
108
+ ids_ratings = [rat.id for rat in ratings]
109
+
110
+ result = (
111
+ ValorDecimal.objects
112
+ .filter(id_dato__id_calificacion_id__in=ids_ratings, id_dato__id_calificacion__id_catador=tester)
113
+ .values(
114
+ nombre_palabra=F("id_dato__id_palabra__nombre_palabra"),
115
+ repeticion=F("id_dato__id_calificacion__num_repeticion"),
116
+ producto_code=F(
117
+ "id_dato__id_calificacion__id_producto__codigoProducto"),
118
+ dato_valor=F("valor")
119
+ )
120
+ )
121
+
122
+ return list(result)
tecnicas/controllers/views_controller/session_management/details_pf_controller.py CHANGED
@@ -1,5 +1,11 @@
1
- from tecnicas.models import SesionSensorial
 
 
 
 
 
2
  from .details_controller import DetallesController
 
3
 
4
 
5
  class DetallesPFController(DetallesController):
@@ -23,11 +29,158 @@ class DetallesPFController(DetallesController):
23
  # Definir el estado de la sesion
24
  rep = technique.repeticion
25
  activate = self.session.activo
26
-
27
  self.context["estado"] = self.getStatus(rep, activate)
28
 
 
 
29
  return self.context
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  def getStatus(self, rep: int, activate: bool):
32
  status = ""
33
 
 
1
+ from django.http import HttpRequest
2
+ from django.shortcuts import redirect
3
+ from django.urls import reverse
4
+ from tecnicas.models import SesionSensorial, Presentador, Participacion, ListaPalabras, Calificacion, Catador
5
+ from tecnicas.controllers import ParticipacionController, DatoController
6
+ from tecnicas.utils import defaultdict_to_dict
7
  from .details_controller import DetallesController
8
+ from collections import defaultdict
9
 
10
 
11
  class DetallesPFController(DetallesController):
 
29
  # Definir el estado de la sesion
30
  rep = technique.repeticion
31
  activate = self.session.activo
 
32
  self.context["estado"] = self.getStatus(rep, activate)
33
 
34
+ self.getDataPhases()
35
+
36
  return self.context
37
 
38
+ def startRepetition(self, presenter: Presentador, request: HttpRequest):
39
+ creator = presenter
40
+ technique = self.session.tecnica
41
+
42
+ if creator.user.username != self.session.creadoPor.user.username:
43
+ return self.getResponse(error="Solo el presentador que crea la sesión puede iniciar la repetición o fase", request=request)
44
+ elif self.session.activo:
45
+ return self.getResponse(error="La sesión ya está activada", request=request)
46
+
47
+ there_participacions = Participacion.objects.filter(
48
+ tecnica=technique).exists()
49
+
50
+ if there_participacions:
51
+ (is_update_participations,
52
+ message) = ParticipacionController.outAllInSession(self.session)
53
+ if not is_update_participations:
54
+ return self.getResponse(error=message, request=request)
55
+
56
+ self.session.activo = True
57
+ technique.repeticion = technique.repeticion + 1
58
+
59
+ technique.save()
60
+ self.session.save()
61
+
62
+ parameters = {
63
+ "session_code": self.session.codigo_sesion
64
+ }
65
+ return redirect(
66
+ reverse(self.url_next, kwargs=parameters))
67
+
68
+ def getDataPhases(self):
69
+ curren_repetition = self.session.tecnica.repeticion
70
+
71
+ if curren_repetition == 1:
72
+ self.context["fisrt_phase"] = self.getDataFirstPhase()
73
+ elif curren_repetition == 2:
74
+ self.context["fisrt_phase"] = self.getDataFirstPhase()
75
+ self.context["second_phase"] = self.getDataSecondPhase()
76
+ elif curren_repetition >= 3:
77
+ self.context["fisrt_phase"] = self.getDataFirstPhase()
78
+ self.context["second_phase"] = self.getDataSecondPhase()
79
+ self.context["data_ratings"] = self.getDataRatings()
80
+
81
+ return self.context
82
+
83
+ def getDataFirstPhase(self):
84
+ lists_testers = ListaPalabras.objects.filter(
85
+ tecnica=self.session.tecnica,
86
+ es_final=False
87
+ )
88
+
89
+ result = []
90
+ for list in lists_testers:
91
+ try:
92
+ username = list.catador.user.username
93
+ except Exception:
94
+ username = None
95
+
96
+ words_qs = list.palabras.all()
97
+ words = []
98
+ for p in words_qs:
99
+ nombre = getattr(p, 'nombre_palabra', None)
100
+ words.append({
101
+ 'id': getattr(p, 'id', None),
102
+ 'nombre_palabra': nombre
103
+ })
104
+
105
+ result.append({
106
+ 'username': username,
107
+ 'words': words
108
+ })
109
+
110
+ return result
111
+
112
+ def getDataSecondPhase(self):
113
+ lists_testers = ListaPalabras.objects.filter(
114
+ tecnica=self.session.tecnica,
115
+ es_final=True
116
+ )
117
+
118
+ result = []
119
+ for list in lists_testers:
120
+ try:
121
+ username = list.catador.user.username
122
+ except Exception:
123
+ username = None
124
+
125
+ words_qs = list.palabras.all()
126
+ words = []
127
+ for p in words_qs:
128
+ nombre = getattr(p, 'nombre_palabra', None)
129
+ words.append({
130
+ 'id': getattr(p, 'id', None),
131
+ 'nombre_palabra': nombre
132
+ })
133
+
134
+ result.append({
135
+ 'username': username,
136
+ 'words': words
137
+ })
138
+
139
+ return result
140
+
141
+ def getDataRatings(self):
142
+ lists_words_testers = self.context["second_phase"]
143
+ technique = self.session.tecnica
144
+
145
+ ratings_for_tester = []
146
+
147
+ for list_tester in lists_words_testers:
148
+ tester_username = list_tester["username"]
149
+ # Se recuperan las calificaciones
150
+ ratings_for_repetition = []
151
+
152
+ ratings = list(Calificacion.objects.filter(
153
+ id_tecnica=technique, id_catador__user__username=tester_username))
154
+
155
+ if not ratings:
156
+ continue
157
+
158
+ data = DatoController.getWordValuesPF(
159
+ ratings=ratings, technique=technique, tester=Catador.objects.get(user__username=tester_username))
160
+
161
+ ratings_for_repetition = defaultdict(lambda: defaultdict(list))
162
+
163
+ for item in data:
164
+ rep = item["repeticion"]
165
+ prod = item["producto_code"]
166
+
167
+ ratings_for_repetition[rep-2][prod].append({
168
+ "nombre_palabra": item["nombre_palabra"],
169
+ "dato_valor": item["dato_valor"]
170
+ })
171
+
172
+ ratings_for_tester.append(
173
+ {
174
+ "tester": tester_username,
175
+ "ratings": defaultdict_to_dict(
176
+ ratings_for_repetition),
177
+ "words": list_tester["words"]
178
+ }
179
+ )
180
+ self.context["existen_calificaciones"] = True
181
+
182
+ return ratings_for_tester
183
+
184
  def getStatus(self, rep: int, activate: bool):
185
  status = ""
186
 
tecnicas/controllers/views_controller/session_management/monitor_escalas_controller.py CHANGED
@@ -1,6 +1,3 @@
1
- from django.http import HttpRequest
2
- from django.shortcuts import render, redirect
3
- from django.urls import reverse
4
  from tecnicas.models import Dato, Participacion
5
  from tecnicas.controllers import SesionController
6
  from .monitor_controller import MonitorController
 
 
 
 
1
  from tecnicas.models import Dato, Participacion
2
  from tecnicas.controllers import SesionController
3
  from .monitor_controller import MonitorController
tecnicas/controllers/views_controller/session_management/monitor_pf_controller.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.http import HttpRequest
2
+ from django.shortcuts import render, redirect
3
+ from django.urls import reverse
4
+ from tecnicas.models import Dato, Participacion, Catador, ListaPalabras, Producto
5
+ from tecnicas.controllers import SesionController
6
+ from .monitor_controller import MonitorController
7
+
8
+
9
+ class MonitorPFController(MonitorController):
10
+ def __init__(self, session: SesionController):
11
+ super().__init__(session)
12
+ self.url_view = "tecnicas/manage_sesions/monitor-session-pf.html"
13
+ self.previus_view = "cata_system:detalles_sesion"
14
+
15
+ def checkAllFinish(self):
16
+ rep = self.sensorial_session.tecnica.repeticion
17
+
18
+ finish_data = ()
19
+
20
+ if rep == 1 or rep == 2:
21
+ finish_data = self.checkFinishFirstPhase()
22
+ elif rep >= 3:
23
+ finish_data = self.checkFinishRepetition()
24
+
25
+ return finish_data
26
+
27
+ def checkFinishFirstPhase(self):
28
+ num_paricipations = Participacion.objects.filter(
29
+ tecnica=self.sensorial_session.tecnica).count()
30
+ if num_paricipations < self.sensorial_session.tecnica.limite_catadores:
31
+ return (False, "No se ha alcanzado el número máximo de catadores")
32
+
33
+ unfinished_participations = Participacion.objects.filter(
34
+ tecnica=self.sensorial_session.tecnica, finalizado=False).count()
35
+ if unfinished_participations:
36
+ return (False, "No todos los catadores han finalizado su evaluación")
37
+
38
+ return (True, "Puedes finalizar la sesión")
39
+
40
+ def checkFinishRepetition(self) -> tuple[bool, str]:
41
+ technique = self.sensorial_session.tecnica
42
+
43
+ # Revisar numero de catadores sea alcanzado
44
+ all_participations = list(
45
+ Participacion.objects.filter(tecnica=technique))
46
+ if len(all_participations) < technique.limite_catadores:
47
+ return (False, "No se ha alcanzado el número máximo de Catadores")
48
+
49
+ # Revisar que cada catador haya terminado de calificar sus palabras
50
+ for particiapation in all_participations:
51
+ expected_ratings_repetition = self.getExpectedRatings(
52
+ tester=particiapation.catador)
53
+
54
+ num_ratings_now = Dato.objects.filter(
55
+ id_calificacion__num_repeticion=technique.repeticion,
56
+ id_calificacion__id_catador=particiapation.catador,
57
+ id_calificacion__id_tecnica=technique
58
+ ).count()
59
+
60
+ if num_ratings_now < expected_ratings_repetition:
61
+ return (False, "No todos los catadores han finalizado su evaluación")
62
+
63
+ return (True, "Puedes finalizar la sesión")
64
+
65
+ def getExpectedRatings(self, tester: Catador):
66
+ num_words = ListaPalabras.objects.get(
67
+ tecnica=self.sensorial_session.tecnica,
68
+ catador=tester,
69
+ es_final=True
70
+ ).palabras.all().count()
71
+
72
+ num_products = Producto.objects.filter(
73
+ id_tecnica=self.sensorial_session.tecnica).count()
74
+
75
+ return num_products * num_words
tecnicas/controllers/views_controller/session_management/monitor_rata_controller.py CHANGED
@@ -1,6 +1,3 @@
1
- from django.http import HttpRequest
2
- from django.shortcuts import render, redirect
3
- from django.urls import reverse
4
  from tecnicas.models import Dato, Participacion
5
  from tecnicas.controllers import SesionController
6
  from .monitor_controller import MonitorController
 
 
 
 
1
  from tecnicas.models import Dato, Participacion
2
  from tecnicas.controllers import SesionController
3
  from .monitor_controller import MonitorController
tecnicas/controllers/views_controller/sessions_tester/{init_session_tester_controller.py → init_session/init_session_controller.py} RENAMED
@@ -1,103 +1,23 @@
1
- from django.db import transaction
2
  from django.http import HttpRequest
3
  from django.shortcuts import render, redirect
4
  from django.urls import reverse
5
- from tecnicas.models import Catador, SesionSensorial, Orden, Participacion, Producto, EsAtributo, Calificacion, EsVocabulario, Dato
6
- from tecnicas.controllers import ParticipacionController
7
  from tecnicas.utils import controller_error, shuffleArray
8
 
9
 
10
- class InitSessionTesterController():
11
  tester: Catador
12
  session: SesionSensorial
13
  order: Orden | dict
 
14
  current_direction = "tecnicas/forms_tester/init_session.html"
15
  escalas_direction = "cata_system:session_convencional"
16
- cata_cirection = "cata_system:session_cata"
17
 
18
  def __init__(self, sensorial_session: SesionSensorial, user_tester: Catador):
19
  self.tester = user_tester
20
  self.session = sensorial_session
21
 
22
- def controllGetEscalas(self, request: HttpRequest):
23
- context = {
24
- "session": self.session,
25
- "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
26
- }
27
-
28
- order = self.checkAndAssignOrder()
29
- if isinstance(order, dict):
30
- context["error"] = order["error"]
31
- return render(request, self.current_direction, context)
32
-
33
- is_end = self.isEndedSessionEscalas()
34
-
35
- request.session["id_order"] = order.id
36
- context["has_ended"] = is_end
37
-
38
- if is_end:
39
- context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
40
-
41
- if "error" in request.GET:
42
- context["error"] = request.GET["error"]
43
-
44
- return render(request, self.current_direction, context)
45
-
46
- def controllPostEscalas(self, request: HttpRequest):
47
- context = {
48
- "session": self.session,
49
- "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
50
- }
51
-
52
- if request.POST["action"] == "start_posting":
53
- parameters = {
54
- "code_sesion": self.session.codigo_sesion
55
- }
56
-
57
- is_end = self.isEndedSessionEscalas()
58
- if is_end:
59
- context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
60
- return render(request, self.current_direction, context)
61
-
62
- update_participation = ParticipacionController.enterSession(
63
- tester=request.user.user_catador, session=self.session)
64
- if isinstance(update_participation, dict):
65
- context["error"] = update_participation["error"]
66
- return render(request, self.current_direction, context)
67
-
68
- request.session["id_participation"] = update_participation.id
69
-
70
- if self.session.tecnica.tipo_tecnica.nombre_tecnica == "cata":
71
- return redirect(reverse(self.cata_cirection, kwargs=parameters))
72
-
73
- return redirect(reverse(self.escalas_direction, kwargs=parameters))
74
-
75
- elif request.POST["action"] == "exit_session":
76
- response = ParticipacionController.outSession(
77
- tester=request.user.user_catador, session=self.session)
78
- if isinstance(response, dict):
79
- context["error"] = response["error"]
80
- return render(request, self.current_direction, context)
81
-
82
- else:
83
- context["error"] = "Acción sin especificar"
84
- return render(request, self.current_direction, context)
85
-
86
- def controllGetRATA(self, request: HttpRequest):
87
- context = {
88
- "session": self.session,
89
- "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
90
- }
91
-
92
- is_end = self.isEndedSessionEscalas()
93
-
94
- context["has_ended"] = is_end
95
-
96
- if is_end:
97
- context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
98
-
99
- return render(request, self.current_direction, context)
100
-
101
  def assignOrder(self):
102
  with transaction.atomic():
103
  orders_without_tester = list(Orden.objects.select_for_update().filter(
@@ -124,7 +44,7 @@ class InitSessionTesterController():
124
  return create
125
  return self.order_to_assign
126
 
127
- def isEndedSessionEscalas(self):
128
  try:
129
  participation = Participacion.objects.get(
130
  catador=self.tester, tecnica=self.session.tecnica)
 
 
1
  from django.http import HttpRequest
2
  from django.shortcuts import render, redirect
3
  from django.urls import reverse
4
+ from django.db import transaction
5
+ from tecnicas.models import Catador, SesionSensorial, Orden, Participacion, Producto, EsAtributo, EsVocabulario, Dato, ListaPalabras
6
  from tecnicas.utils import controller_error, shuffleArray
7
 
8
 
9
+ class InitSessionController():
10
  tester: Catador
11
  session: SesionSensorial
12
  order: Orden | dict
13
+ current_direction: str
14
  current_direction = "tecnicas/forms_tester/init_session.html"
15
  escalas_direction = "cata_system:session_convencional"
 
16
 
17
  def __init__(self, sensorial_session: SesionSensorial, user_tester: Catador):
18
  self.tester = user_tester
19
  self.session = sensorial_session
20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  def assignOrder(self):
22
  with transaction.atomic():
23
  orders_without_tester = list(Orden.objects.select_for_update().filter(
 
44
  return create
45
  return self.order_to_assign
46
 
47
+ def isEndedSession(self):
48
  try:
49
  participation = Participacion.objects.get(
50
  catador=self.tester, tecnica=self.session.tecnica)
tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_escalas_controller.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.http import HttpRequest
2
+ from django.shortcuts import render, redirect
3
+ from django.urls import reverse
4
+ from tecnicas.models import Catador, SesionSensorial, Orden
5
+ from tecnicas.controllers import ParticipacionController
6
+ from .init_session_controller import InitSessionController
7
+
8
+
9
+ class InitSessionEscalasController(InitSessionController):
10
+ tester: Catador
11
+ session: SesionSensorial
12
+ order: Orden | dict
13
+
14
+ def __init__(self, sensorial_session, user_tester):
15
+ super().__init__(sensorial_session, user_tester)
16
+ self.current_direction = "tecnicas/forms_tester/init_session.html"
17
+ self.escalas_direction = "cata_system:session_convencional"
18
+ self.cata_direction = "cata_system:session_cata"
19
+
20
+ def controllGet(self, request: HttpRequest):
21
+ context = {
22
+ "session": self.session,
23
+ "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
24
+ }
25
+
26
+ order = self.checkAndAssignOrder()
27
+ if isinstance(order, dict):
28
+ context["error"] = order["error"]
29
+ return render(request, self.current_direction, context)
30
+
31
+ is_end = self.isEndedSession()
32
+
33
+ request.session["id_order"] = order.id
34
+ context["has_ended"] = is_end
35
+
36
+ if is_end:
37
+ context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
38
+
39
+ if "error" in request.GET:
40
+ context["error"] = request.GET["error"]
41
+
42
+ return render(request, self.current_direction, context)
43
+
44
+ def controllPost(self, request: HttpRequest):
45
+ context = {
46
+ "session": self.session,
47
+ "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
48
+ }
49
+
50
+ if request.POST["action"] == "start_posting":
51
+ parameters = {
52
+ "code_sesion": self.session.codigo_sesion
53
+ }
54
+
55
+ is_end = self.isEndedSession()
56
+ if is_end:
57
+ context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
58
+ return render(request, self.current_direction, context)
59
+
60
+ update_participation = ParticipacionController.enterSession(
61
+ tester=request.user.user_catador, session=self.session)
62
+ if isinstance(update_participation, dict):
63
+ context["error"] = update_participation["error"]
64
+ return render(request, self.current_direction, context)
65
+
66
+ request.session["id_participation"] = update_participation.id
67
+
68
+ if self.session.tecnica.tipo_tecnica.nombre_tecnica == "cata":
69
+ return redirect(reverse(self.cata_direction, kwargs=parameters))
70
+
71
+ return redirect(reverse(self.escalas_direction, kwargs=parameters))
72
+
73
+ elif request.POST["action"] == "exit_session":
74
+ response = ParticipacionController.outSession(
75
+ tester=request.user.user_catador, session=self.session)
76
+ if isinstance(response, dict):
77
+ context["error"] = response["error"]
78
+ return render(request, self.current_direction, context)
79
+
80
+ else:
81
+ context["error"] = "Acción sin especificar"
82
+ return render(request, self.current_direction, context)
tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_pf_controller.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.http import HttpRequest
2
+ from django.shortcuts import render, redirect
3
+ from django.urls import reverse
4
+ from tecnicas.models import Participacion, Producto, Dato, ListaPalabras
5
+ from tecnicas.controllers import ParticipacionController
6
+ from .init_session_controller import InitSessionController
7
+
8
+
9
+ class InitSessionPFController(InitSessionController):
10
+ def __init__(self, sensorial_session, user_tester):
11
+ super().__init__(sensorial_session, user_tester)
12
+ self.current_direction = "tecnicas/forms_tester/init_session_pf.html"
13
+ self.pf_direction = "cata_system:session_pf"
14
+
15
+ def controllGet(self, request: HttpRequest):
16
+ context = {
17
+ "session": self.session,
18
+ "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
19
+ }
20
+
21
+ (is_end, message, rep_show) = self.isEndedSession()
22
+
23
+ context["has_ended"] = is_end
24
+ context["activity"] = message
25
+ context["repetition"] = rep_show
26
+
27
+ if "error" in request.GET:
28
+ context["error"] = request.GET["error"]
29
+
30
+ return render(request, self.current_direction, context)
31
+
32
+ def controllPost(self, request: HttpRequest):
33
+ context = {
34
+ "session": self.session,
35
+ "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
36
+ }
37
+
38
+ action = request.POST["action"]
39
+
40
+ if action == "start_posting":
41
+ parameters = {
42
+ "code_sesion": self.session.codigo_sesion
43
+ }
44
+
45
+ (is_end, message, rep_show) = self.isEndedSession()
46
+ if is_end:
47
+ return self.controllGet(request)
48
+
49
+ update_participation = ParticipacionController.enterSession(
50
+ tester=request.user.user_catador, session=self.session)
51
+ if isinstance(update_participation, dict):
52
+ context["error"] = update_participation["error"]
53
+ return render(request, self.current_direction, context)
54
+
55
+ request.session["id_participation"] = update_participation.id
56
+
57
+ return redirect(reverse(self.pf_direction, kwargs=parameters))
58
+
59
+ elif action == "exit_session":
60
+ response = ParticipacionController.outSession(
61
+ tester=request.user.user_catador, session=self.session)
62
+ if isinstance(response, dict):
63
+ context["error"] = response["error"]
64
+ return self.controllGet(request)
65
+
66
+ else:
67
+ context["error"] = "Acción sin especificar"
68
+ return render(request, self.current_direction, context)
69
+
70
+ def isEndedSession(self) -> tuple[bool, str, int]:
71
+ rep = self.session.tecnica.repeticion
72
+
73
+ is_end = False
74
+ message = ""
75
+ repetitiom_show = 0
76
+
77
+ if rep == 1:
78
+ is_end = self.endedSessionMakeList()
79
+ message = "Ya has creado la Lista de palabras inicial" if is_end else "Debes crear tu lista de palabras inicial"
80
+ repetitiom_show = 0
81
+ elif rep == 2:
82
+ is_end = self.endedSessionMakeList()
83
+ message = "Ya has creado la Lista de palabras final" if is_end else "Debes crear tu lista de palabras final"
84
+ repetitiom_show = 0
85
+ elif rep >= 3:
86
+ is_end = self.endedSessionRepetition()
87
+ message = "Has finalizado con el proceso de calificación" if is_end else "Debe hacer tu proceso de calificación"
88
+ repetitiom_show = rep - 2
89
+ else:
90
+ message = "Parece que la repetición es cero, no es posible hacer algo ahora mismo"
91
+
92
+ return (is_end, message, repetitiom_show)
93
+
94
+ def endedSessionMakeList(self):
95
+ try:
96
+ return Participacion.objects.get(
97
+ catador=self.tester, tecnica=self.session.tecnica).finalizado
98
+ except Participacion.DoesNotExist:
99
+ print("No se ha encontrado la participación")
100
+ return False
101
+
102
+ def endedSessionRepetition(self):
103
+ try:
104
+ participation = Participacion.objects.get(
105
+ catador=self.tester, tecnica=self.session.tecnica)
106
+ self.session.refresh_from_db()
107
+
108
+ # ////////////////////////////////////////////////////////////// #
109
+ #
110
+ # numero_datos_esperadas = num_productos * num_palabras
111
+ # Si numero_datos_esperadas es igual a numero_datos_actuales en la repetcion R
112
+ # Ha terminado la repeticion
113
+ #
114
+ # ////////////////////////////////////////////////////////////// #
115
+
116
+ if participation.finalizado:
117
+ num_products = Producto.objects.filter(
118
+ id_tecnica=self.session.tecnica).count()
119
+
120
+ num_words = ListaPalabras.objects.get(
121
+ tecnica=self.session.tecnica,
122
+ catador=self.tester,
123
+ es_final=True
124
+ ).palabras.all().count()
125
+
126
+ expected_ratings_repetition = num_products * num_words
127
+
128
+ technique = self.session.tecnica
129
+ num_ratings_now = Dato.objects.filter(
130
+ id_calificacion__id_catador=self.tester,
131
+ id_calificacion__id_tecnica=technique,
132
+ id_calificacion__num_repeticion=technique.repeticion
133
+ ).count()
134
+
135
+ is_end = num_ratings_now >= expected_ratings_repetition
136
+
137
+ return is_end
138
+
139
+ else:
140
+ return participation.finalizado
141
+
142
+ except Participacion.DoesNotExist:
143
+ print("No se ha encontrado la participación")
144
+ return False
tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_rata_controller.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.http import HttpRequest
2
+ from django.shortcuts import render
3
+ from .init_session_controller import InitSessionController
4
+
5
+
6
+ class InitSessionRATAController(InitSessionController):
7
+ def __init__(self, sensorial_session, user_tester):
8
+ super().__init__(sensorial_session, user_tester)
9
+ self.current_direction = "tecnicas/forms_tester/init_session.html"
10
+
11
+ def controllGet(self, request: HttpRequest):
12
+ context = {
13
+ "session": self.session,
14
+ "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
15
+ }
16
+
17
+ is_end = self.isEndedSession()
18
+
19
+ context["has_ended"] = is_end
20
+
21
+ if is_end:
22
+ context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
23
+
24
+ return render(request, self.current_direction, context)
tecnicas/controllers/views_controller/sessions_tester/login_session_tester_controller.py CHANGED
@@ -1,4 +1,4 @@
1
- from django.http import HttpRequest, JsonResponse
2
  from django.shortcuts import render, redirect
3
  from django.urls import reverse
4
  from django.db import transaction
@@ -97,3 +97,6 @@ class LoginSessionTesterController():
97
  else:
98
  context["error"] = "Imposible acceder a esta sesión"
99
  return render(request, self.current_direcction, context)
 
 
 
 
1
+ from django.http import HttpRequest
2
  from django.shortcuts import render, redirect
3
  from django.urls import reverse
4
  from django.db import transaction
 
97
  else:
98
  context["error"] = "Imposible acceder a esta sesión"
99
  return render(request, self.current_direcction, context)
100
+
101
+ def validateEntryPF(self, request=HttpRequest):
102
+ return self.validateEntryEscalas(request=request)
tecnicas/controllers/views_controller/sessions_tester/{general_test_controller.py → tests_forms/general_test_controller.py} RENAMED
File without changes
tecnicas/controllers/views_controller/sessions_tester/{test_cata_controller.py → tests_forms/test_cata_controller.py} RENAMED
File without changes
tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_pf_controller.py ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.http import HttpRequest
2
+ from django.shortcuts import redirect, render
3
+ from django.urls import reverse
4
+ from tecnicas.models import Producto, Participacion, Palabra, Calificacion, ListaPalabras
5
+ from tecnicas.controllers import ParticipacionController, PalabrasController, EscalaController
6
+ from tecnicas.forms import ListWordsForm
7
+ from .general_test_controller import GenetalTestController
8
+
9
+
10
+ class TestPFController(GenetalTestController):
11
+ def __init__(self, sensorial_session, user_tester):
12
+ super().__init__(sensorial_session, user_tester)
13
+
14
+ def controllGet(self, request: HttpRequest, error=""):
15
+ self.participation = Participacion.objects.get(
16
+ tecnica=self.session.tecnica, catador=request.user.user_catador)
17
+ self.context["session"] = self.session
18
+
19
+ if error:
20
+ self.context["error"] = error
21
+
22
+ rep = self.session.tecnica.repeticion
23
+
24
+ if rep == 1:
25
+ self.current_directory = "tecnicas/forms_tester/test_pf_list_words.html"
26
+ response = self.getFirstPhase(request)
27
+ elif rep == 2:
28
+ self.current_directory = "tecnicas/forms_tester/test_pf_list_words.html"
29
+ response = self.getSecondPhase(request)
30
+ elif rep >= 3:
31
+ self.current_directory = "tecnicas/forms_tester/test_pf_rating_list.html"
32
+ response = self.getRepetitionPhase(request)
33
+ else:
34
+ response = self.getErrorRepetition(request)
35
+
36
+ return response
37
+
38
+ def controllPost(self, request: HttpRequest):
39
+ action = request.POST["action"]
40
+
41
+ if action == "finish_session":
42
+ self.participation = Participacion.objects.get(
43
+ tecnica=self.session.tecnica, catador=request.user.user_catador)
44
+ ParticipacionController.finishSession(self.participation)
45
+ params = {"code_sesion": self.session.codigo_sesion}
46
+ return redirect(reverse(self.previus_directory, kwargs=params))
47
+
48
+ else:
49
+ return self.controllGet(request, error="Acción no permitida")
50
+
51
+ def getFirstPhase(self, request: HttpRequest):
52
+ self.participation.refresh_from_db()
53
+
54
+ if self.participation.finalizado:
55
+ params = {
56
+ "code_sesion": self.session.codigo_sesion
57
+ }
58
+ return redirect(reverse(self.previus_directory, kwargs=params))
59
+
60
+ self.context["form"] = ListWordsForm()
61
+ self.context["initial_phase"] = True
62
+
63
+ try:
64
+ tester_list = ListaPalabras.objects.get(
65
+ tecnica=self.session.tecnica,
66
+ catador=request.user.user_catador,
67
+ es_final=False
68
+ )
69
+ list_words = list(tester_list.palabras.all())
70
+ self.context["words"] = list_words
71
+ except ListaPalabras.DoesNotExist:
72
+ self.context["words"] = []
73
+
74
+ return render(request, self.current_directory, self.context)
75
+
76
+ def getSecondPhase(self, request: HttpRequest):
77
+ self.participation.refresh_from_db()
78
+
79
+ if self.participation.finalizado:
80
+ params = {
81
+ "code_sesion": self.session.codigo_sesion
82
+ }
83
+ return redirect(reverse(self.previus_directory, kwargs=params))
84
+
85
+ try:
86
+ tester_list = ListaPalabras.objects.get(
87
+ tecnica=self.session.tecnica,
88
+ catador=request.user.user_catador,
89
+ es_final=True
90
+ )
91
+ except ListaPalabras.DoesNotExist:
92
+ tester_list = ListaPalabras.objects.get(
93
+ tecnica=self.session.tecnica,
94
+ catador=request.user.user_catador,
95
+ es_final=False
96
+ )
97
+
98
+ list_words = list(tester_list.palabras.all())
99
+
100
+ self.context["form"] = ListWordsForm()
101
+ self.context["initial_phase"] = False
102
+ self.context["words"] = list_words
103
+
104
+ return render(request, self.current_directory, self.context)
105
+
106
+ def getRepetitionPhase(self, request: HttpRequest):
107
+ technique = self.session.tecnica
108
+
109
+ products_in_technique = Producto.objects.filter(id_tecnica=technique)
110
+
111
+ words = list(
112
+ ListaPalabras.objects.get(
113
+ tecnica=self.session.tecnica,
114
+ catador=request.user.user_catador,
115
+ es_final=True
116
+ ).palabras.all()
117
+ )
118
+
119
+ use_product: Producto = None
120
+ use_words: list[Palabra] = None
121
+
122
+ # Revisamos el producto que le falten calificaciones
123
+ for current_product in products_in_technique:
124
+ try:
125
+ rating = Calificacion.objects.get(
126
+ num_repeticion=technique.repeticion,
127
+ id_producto=current_product,
128
+ id_tecnica=technique,
129
+ id_catador=self.tester
130
+ )
131
+ except Calificacion.DoesNotExist:
132
+ # Si no hay calificacion mandamos el producto actual y todas la palabras
133
+ use_product = current_product
134
+ use_words = words
135
+ break
136
+
137
+ # Obtener los datos asociados para la calificacion para ver que palabras quedan por calificar
138
+ recoreded_data = rating.dato_calificacion.all()
139
+
140
+ if not recoreded_data:
141
+ # Si no hay datos entonces devolver el producto con todas las palabras
142
+ use_product = current_product
143
+ use_words = words
144
+ break
145
+ else:
146
+ words_to_use = PalabrasController.getWordsWithoutData(
147
+ recoreded_data=recoreded_data, words=words)
148
+
149
+ # Si quedan palabras por calificar mandar las palabras con el producto
150
+ if not isinstance(words_to_use, dict) and words_to_use:
151
+ use_product = current_product
152
+ use_words = words_to_use
153
+ break
154
+
155
+ # Si no hay producto que falta por calificar finalizar sesion para el Catador
156
+ if not use_product:
157
+ updated_participation = ParticipacionController.finishSession(
158
+ self.participation)
159
+ params = {
160
+ "code_sesion": self.session.codigo_sesion
161
+ }
162
+ return redirect(reverse(self.previus_directory, kwargs=params))
163
+
164
+ scale = EscalaController.getScaleByTechnique(technique=technique)
165
+ use_tags = EscalaController.getRelatedTagsInScale(scale=scale)
166
+
167
+ self.context["product"] = use_product
168
+ self.context["words"] = use_words
169
+
170
+ self.context["scale"] = scale
171
+ self.context["type_scale"] = scale.id_tipo_escala.nombre_escala
172
+ self.context["tags"] = use_tags
173
+
174
+ self.context["repetition"] = technique.repeticion - 2
175
+
176
+ if self.context["type_scale"] == "continua":
177
+ self.context["size_scale"] = {
178
+ "max_size": scale.longitud * 100,
179
+ "middle_size": (scale.longitud * 100)/2
180
+ }
181
+
182
+ return render(request, self.current_directory, self.context)
183
+
184
+ def getErrorRepetition(self, request: HttpRequest):
185
+ params = {
186
+ "code_sesion": self.session.codigo_sesion
187
+ }
188
+ return redirect(reverse(self.previus_directory, kwargs=params))
tecnicas/controllers/views_controller/sessions_tester/{test_rata_controller.py → tests_forms/test_rata_controller.py} RENAMED
File without changes
tecnicas/controllers/views_controller/sessions_tester/{test_scales_controller.py → tests_forms/test_scales_controller.py} RENAMED
@@ -78,5 +78,10 @@ class TestScalesController(GenetalTestController):
78
  ctx["scale"] = scale
79
  ctx["type_scale"] = scale.id_tipo_escala.nombre_escala
80
  ctx["tags"] = EscalaController.getRelatedTagsInScale(scale=scale)
 
 
 
 
 
81
 
82
- return render(request, self.current_directory, ctx)
 
78
  ctx["scale"] = scale
79
  ctx["type_scale"] = scale.id_tipo_escala.nombre_escala
80
  ctx["tags"] = EscalaController.getRelatedTagsInScale(scale=scale)
81
+ if ctx["type_scale"] == "continua":
82
+ ctx["size_scale"] = {
83
+ "max_size": scale.longitud * 100,
84
+ "middle_size": (scale.longitud * 100)/2
85
+ }
86
 
87
+ return render(request, self.current_directory, ctx)
tecnicas/forms/__init__.py CHANGED
@@ -8,3 +8,5 @@ from .codes_form import CodesForm
8
  from .catador_form import CatadorForm
9
  from .word_form import WordForm
10
  from .vocabulary_select import VocabularioSelectForm
 
 
 
8
  from .catador_form import CatadorForm
9
  from .word_form import WordForm
10
  from .vocabulary_select import VocabularioSelectForm
11
+
12
+ from .list_words_form import ListWordsForm
tecnicas/forms/list_words_form.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django import forms
2
+ import re
3
+
4
+
5
+ class ListWordsForm(forms.Form):
6
+ regex = re.compile(r'^[a-zñ]{3,}$')
7
+
8
+ def __init__(self, *args, new_words=None, **kwargs):
9
+ super().__init__(*args, **kwargs)
10
+
11
+ if not new_words:
12
+ self.fields['nombre_palabra'] = forms.CharField(
13
+ label='Nombre de la palabra',
14
+ max_length=255,
15
+ widget=forms.TextInput(attrs={
16
+ 'placeholder': 'Escribe una palabra',
17
+ 'class': 'cts-input-list-word bg-surface-ligt p-1 border-b-1 text-center w-full',
18
+ 'pattern': self.regex.pattern,
19
+ 'title': 'Solo letras minúsculas y la letra ñ, no poner acentos. No se permiten mayúsculas ni caracteres especiales. Mínimo 3 letras',
20
+ })
21
+ )
22
+ else:
23
+ for index, name in enumerate(new_words, start=1):
24
+ self.fields[f'palabra_{index}'] = forms.CharField(
25
+ label=f'Palabra {index}',
26
+ max_length=255,
27
+ initial=name,
28
+ widget=forms.TextInput(attrs={
29
+ 'placeholder': 'Escribe una palabra',
30
+ 'class': 'cts-input-list-word bg-surface-ligt p-1 border-b-1 text-center w-full',
31
+ 'pattern': self.regex.pattern,
32
+ 'title': 'Solo letras minúsculas y la letra ñ. No se permiten mayúsculas ni caracteres especiales.'
33
+ })
34
+ )
35
+
36
+ def clean(self):
37
+ cleaned_data = super().clean()
38
+ errores = []
39
+
40
+ for field_name, value in cleaned_data.items():
41
+ if not self.regex.match(value):
42
+ errores.append(f"'{value}' contiene caracteres no permitidos.")
43
+ elif len(value) > 255:
44
+ errores.append(f"'{value}' excede los 255 caracteres.")
45
+
46
+ if errores:
47
+ raise forms.ValidationError(errores)
48
+
49
+ return cleaned_data
tecnicas/migrations/0022_alter_sesionsensorial_codigo_sesion.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 5.2.1 on 2025-11-12 20:16
2
+
3
+ import shortuuid.main
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('tecnicas', '0021_rename_nomre_vocabulario_vocabulario_nombre_vocabulario_and_more'),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AlterField(
15
+ model_name='sesionsensorial',
16
+ name='codigo_sesion',
17
+ field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False),
18
+ ),
19
+ ]
tecnicas/migrations/0023_alter_sesionsensorial_codigo_sesion_listapalabras.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 5.2.1 on 2025-11-12 20:24
2
+
3
+ import django.db.models.deletion
4
+ import shortuuid.main
5
+ from django.db import migrations, models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+
10
+ dependencies = [
11
+ ('tecnicas', '0022_alter_sesionsensorial_codigo_sesion'),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AlterField(
16
+ model_name='sesionsensorial',
17
+ name='codigo_sesion',
18
+ field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False),
19
+ ),
20
+ migrations.CreateModel(
21
+ name='ListaPalabras',
22
+ fields=[
23
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
24
+ ('es_final', models.BooleanField(default=False)),
25
+ ('catador', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='catador_lista', to='tecnicas.catador')),
26
+ ('palabras', models.ManyToManyField(related_name='lista_palabras', to='tecnicas.palabra')),
27
+ ('tecnica', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tecnica_lista', to='tecnicas.tecnica')),
28
+ ],
29
+ ),
30
+ ]
tecnicas/models/__init__.py CHANGED
@@ -27,4 +27,6 @@ from .dato_valor import ValorBooleano
27
  from .orden import Orden
28
  from .orden import Posicion
29
 
30
- from .participacion import Participacion
 
 
 
27
  from .orden import Orden
28
  from .orden import Posicion
29
 
30
+ from .participacion import Participacion
31
+
32
+ from .lista_palabras import ListaPalabras
tecnicas/models/lista_palabras.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.db import models
2
+ from tecnicas.models import Tecnica, Catador, Palabra
3
+
4
+
5
+ class ListaPalabras(models.Model):
6
+ tecnica = models.ForeignKey(
7
+ Tecnica, on_delete=models.CASCADE, related_name="tecnica_lista")
8
+ catador = models.ForeignKey(
9
+ Catador, on_delete=models.CASCADE, related_name="catador_lista")
10
+ es_final = models.BooleanField(default=False)
11
+ palabras = models.ManyToManyField(
12
+ Palabra, related_name="lista_palabras")
13
+
14
+ def __str__(self):
15
+ return f"{self.tecnica.sesion_tecnica.codigo_sesion} - {self.catador.user.username}"
tecnicas/static/js/download-table-zip.js ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener("DOMContentLoaded", function () {
2
+ const btn = document.getElementById("download-csv-btn");
3
+ if (!btn) return;
4
+
5
+ btn.addEventListener("click", async function () {
6
+ if (typeof JSZip === "undefined" || typeof saveAs === "undefined") {
7
+ alert(
8
+ "Librerías JSZip o FileSaver no están cargadas. Asegúrate de incluir los CDN de JSZip y FileSaver."
9
+ );
10
+ return;
11
+ }
12
+
13
+ const sections = document.querySelectorAll("section[data-tester]");
14
+ if (!sections || sections.length === 0) {
15
+ alert("No hay tablas para descargar.");
16
+ return;
17
+ }
18
+
19
+ const zip = new JSZip();
20
+
21
+ let zipName = "";
22
+
23
+ sections.forEach((sec) => {
24
+ const tester = sec.getAttribute("data-tester") || "tester";
25
+ const sessionName = sec.getAttribute("data-session-name") || "";
26
+ const sessionCode = sec.getAttribute("data-session-code") || "";
27
+
28
+ const fileName = `${tester}_${
29
+ sessionName ? sessionName.trim() : sessionCode.trim()
30
+ }`;
31
+
32
+ zipName = `datos_sesion_${
33
+ sessionName ? sessionName.trim() : sessionCode.trim()
34
+ }`;
35
+
36
+ const table = sec.querySelector("table");
37
+ if (!table) return;
38
+
39
+ // Build CSV
40
+ const rows = [];
41
+ const headerCells = Array.from(table.querySelectorAll("thead tr th"));
42
+ const headers = headerCells.map((h) => h.textContent.trim());
43
+ rows.push(headers.map(escapeCsv).join(","));
44
+
45
+ const trs = table.querySelectorAll("tbody tr");
46
+ trs.forEach((tr) => {
47
+ const tds = Array.from(tr.querySelectorAll("td"));
48
+ const values = tds.map((td) => escapeCsv(td.textContent.trim()));
49
+ rows.push(values.join(","));
50
+ });
51
+
52
+ const csvContent = rows.join("\r\n");
53
+ zip.file(`${fileName}.csv`, csvContent);
54
+ });
55
+
56
+ try {
57
+ const blob = await zip.generateAsync({ type: "blob" });
58
+ const zipNameWithExtension = zipName + ".zip";
59
+ saveAs(blob, zipNameWithExtension);
60
+ } catch (err) {
61
+ console.error(err);
62
+ alert("Error al generar el ZIP: " + err.message);
63
+ }
64
+ });
65
+
66
+ const escapeCsv = (val) => {
67
+ if (val == null) return "";
68
+ let normalVal = val.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
69
+ const needsQuotes = /[",\n,]/.test(normalVal);
70
+ let v = String(normalVal).replace(/"/g, '""');
71
+ if (needsQuotes) v = `"${v}"`;
72
+ return v;
73
+ };
74
+ });
tecnicas/static/js/lists-words-testers.js ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ async function getListWordsTesters() {
2
+ const URL = "/cata/testers/api/ratingword/pf/list";
3
+ try {
4
+ const response = await fetch(URL, {
5
+ method: "GET",
6
+ });
7
+
8
+ if (!response.ok) {
9
+ spanNotifaction("Fallo con la respuesta recibida");
10
+ return false;
11
+ }
12
+
13
+ const result = await response.json();
14
+ const messError = result.error;
15
+ if (messError) {
16
+ spanNotifaction(messError);
17
+ const containerWords = document.querySelector(".cts-content-list-words");
18
+ containerWords.innerHTML = `
19
+ <p class="bg-surface-sweet font-bold text-center text-lg px-4 pt-2 pb-3 rounded w-full max-sm:mx-2">
20
+ ${messError}
21
+ </p>`;
22
+ return false;
23
+ }
24
+ spanNotifaction(result.message, false);
25
+ const containerWords = document.querySelector(".cts-content-list-words");
26
+ containerWords.innerHTML = "";
27
+
28
+ const listWordsTesters = result.lists_words;
29
+
30
+ listWordsTesters.forEach((listTester) => {
31
+ const username = listTester.username;
32
+ const words = listTester.words;
33
+ const status = listTester.status;
34
+ const listDiv = createListWords(username, words, status);
35
+ containerWords.appendChild(listDiv);
36
+ });
37
+
38
+ return true;
39
+ } catch (error) {
40
+ console.error(error);
41
+ spanNotifaction("Error del servidor con la API");
42
+ return false;
43
+ }
44
+ }
45
+
46
+ function createListWords(username, words, status = "indefinido") {
47
+ const div = document.createElement("div");
48
+ const paragraph = document.createElement("p");
49
+ const paragraphStatus = document.createElement("p");
50
+ const ul = document.createElement("ul");
51
+
52
+ div.classList.add(
53
+ "cts-item-list-tester",
54
+ "bg-surface-sweet",
55
+ "px-4",
56
+ "pt-2",
57
+ "pb-3",
58
+ "rounded",
59
+ "shrink-0",
60
+ "w-64",
61
+ "space-y-2"
62
+ );
63
+
64
+ paragraph.classList.add(
65
+ "bg-surface-card",
66
+ "text-lg",
67
+ "font-semibold",
68
+ "text-center",
69
+ "rounded"
70
+ );
71
+
72
+ paragraphStatus.classList.add(
73
+ "bg-surface-card",
74
+ "text-lg",
75
+ "font-semibold",
76
+ "text-center",
77
+ "rounded"
78
+ );
79
+
80
+ paragraph.textContent = username;
81
+ paragraphStatus.textContent = status;
82
+
83
+ ul.classList.add("text-center", "grid", "grid-cols-2", "gap-2", "w-full");
84
+
85
+ words.forEach((word) => {
86
+ const li = document.createElement("li");
87
+ li.classList.add(
88
+ "bg-surface-ligt",
89
+ "rounded",
90
+ "font-bold",
91
+ "py-1",
92
+ "px-2",
93
+ "truncate"
94
+ );
95
+ li.textContent = word.nombre_palabra;
96
+ ul.appendChild(li);
97
+ });
98
+
99
+ div.appendChild(paragraph);
100
+ div.appendChild(paragraphStatus);
101
+ div.appendChild(ul);
102
+ return div;
103
+ }
tecnicas/static/js/pf-make-list.js ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const FORM_DESCRIBE = document.querySelector(".cts-form-pf-word");
2
+ const BOX_WORDS = document.querySelector(".cts-box-words");
3
+ const IMG_LIST = document.querySelector(".cts-img-list");
4
+ const ERROR_INPUT_WORD = document.querySelector(".error-input-word");
5
+ const FORM_ACTION = document.querySelector(".form-actions");
6
+
7
+ const WORDS = [];
8
+ const STYLES_LI = [
9
+ "cts-item-words",
10
+ "bg-gray-400",
11
+ "text-black",
12
+ "rounded",
13
+ "font-bold",
14
+ "text-lg",
15
+ "px-4",
16
+ "py-3",
17
+ "flex",
18
+ "flex-wrap",
19
+ "flex-row",
20
+ "flex-1",
21
+ "min-w-fit",
22
+ "justify-center",
23
+ "items-center",
24
+ "gap-3",
25
+ ];
26
+
27
+ const STYLES_BTN = [
28
+ "cts-remove-word",
29
+ "px-4",
30
+ "border-b-2",
31
+ "active:border-b-0",
32
+ "active:border-t-2",
33
+ "transition-all",
34
+ "rounded-xl",
35
+ "font-black",
36
+ "w-fit",
37
+ "capitalize",
38
+ "active:border-red-500",
39
+ "border-red-800",
40
+ "bg-red-500",
41
+ ];
42
+
43
+ const itemWord = (wordName, index) => {
44
+ const btn = document.createElement("button");
45
+ btn.setAttribute("data-index", index);
46
+ btn.classList.add(...STYLES_BTN);
47
+ btn.textContent = "➖";
48
+
49
+ const ph = document.createElement("p");
50
+ ph.classList.add("ct-word-received");
51
+ ph.textContent = wordName;
52
+
53
+ const li = document.createElement("li");
54
+ li.setAttribute("id", `word-${index}`);
55
+ li.classList.add(...STYLES_LI);
56
+
57
+ li.appendChild(ph);
58
+ li.appendChild(btn);
59
+
60
+ return li;
61
+ };
62
+
63
+ function initWordsFromBox() {
64
+ if (!BOX_WORDS) return;
65
+ const current_words = BOX_WORDS.querySelectorAll(".cts-item-words");
66
+ if (!current_words.length) return;
67
+
68
+ WORDS.length = 0;
69
+ current_words.forEach((li) => {
70
+ const p = li.querySelector(".ct-word-received");
71
+ const text = p ? p.textContent.trim() : li.textContent.trim();
72
+ if (text) WORDS.push(text);
73
+ });
74
+ renderWords();
75
+ }
76
+
77
+ function renderWords() {
78
+ if (!BOX_WORDS) return;
79
+ if (!WORDS.length) {
80
+ BOX_WORDS.innerHTML = "";
81
+ IMG_LIST.classList.remove("hidden");
82
+ BOX_WORDS.appendChild(IMG_LIST);
83
+ return;
84
+ }
85
+
86
+ BOX_WORDS.innerHTML = "";
87
+ WORDS.forEach((word, index) => {
88
+ let liElement = itemWord(word, index);
89
+ BOX_WORDS.appendChild(liElement);
90
+ });
91
+
92
+ const removeButtons = BOX_WORDS.querySelectorAll("button.cts-remove-word");
93
+ removeButtons.forEach((btn) => {
94
+ btn.addEventListener("click", (e) => {
95
+ const idx = parseInt(btn.dataset.index);
96
+ if (!Number.isNaN(idx)) {
97
+ WORDS.splice(idx, 1);
98
+ renderWords();
99
+ }
100
+ });
101
+ });
102
+ }
103
+
104
+ function setupDescribeFormToAddWord() {
105
+ if (!FORM_DESCRIBE) return;
106
+ FORM_DESCRIBE.addEventListener("submit", (e) => {
107
+ e.preventDefault();
108
+
109
+ if (!FORM_DESCRIBE.reportValidity()) {
110
+ return;
111
+ }
112
+
113
+ const input = FORM_DESCRIBE.querySelector('input[type="text"]');
114
+ if (!input) return;
115
+
116
+ const value = input.value.trim();
117
+ if (!value) return;
118
+
119
+ if (WORDS.includes(value)) {
120
+ spanNotifaction("Esa palabra ya está en la lista");
121
+ return;
122
+ }
123
+
124
+ WORDS.push(value);
125
+ renderWords();
126
+
127
+ input.value = "";
128
+ input.focus();
129
+ });
130
+ }
131
+
132
+ function spanNotifaction(messageError, isError = true) {
133
+ const span = document.createElement("span");
134
+ span.textContent = messageError;
135
+
136
+ const div = document.createElement("div");
137
+ div.classList.add("alert", isError ? "alert-error" : "alert-success");
138
+ div.appendChild(span);
139
+
140
+ ERROR_INPUT_WORD.append(div);
141
+
142
+ setTimeout(() => {
143
+ ERROR_INPUT_WORD.removeChild(div);
144
+ }, 3000);
145
+ }
146
+
147
+ async function sendWordsToSave() {
148
+ if (!WORDS.length) {
149
+ spanNotifaction("Debe existir al menos una palabra en la lista");
150
+ return false;
151
+ }
152
+
153
+ const currentPhase = parseInt(
154
+ document.querySelector(".cts-phase-pf").dataset.phase
155
+ );
156
+
157
+ const csrfToken = document.querySelector("[name=csrfmiddlewaretoken]").value;
158
+
159
+ const requestData = {
160
+ phase: currentPhase,
161
+ words: WORDS,
162
+ };
163
+
164
+ const URL = "/cata/testers/api/ratingword/pf/list";
165
+
166
+ try {
167
+ const response = await fetch(URL, {
168
+ method: "POST",
169
+ headers: {
170
+ "Content-Type": "application/json",
171
+ "X-CSRFToken": csrfToken,
172
+ "X-Requested-With": "XMLHttpRequest",
173
+ },
174
+ body: JSON.stringify(requestData),
175
+ });
176
+
177
+ if (!response.ok) {
178
+ spanNotifaction("Fallo con la respuesta recibida");
179
+ return false;
180
+ }
181
+
182
+ const result = await response.json();
183
+
184
+ const messError = result.error;
185
+
186
+ if (messError) {
187
+ spanNotifaction(messError);
188
+ return false;
189
+ }
190
+
191
+ spanNotifaction(result.message, false);
192
+ const addedWords = result.words;
193
+ WORDS.length = 0;
194
+ addedWords.forEach((word) => WORDS.push(word));
195
+ renderWords();
196
+ return true;
197
+ } catch (err) {
198
+ console.error(err);
199
+ spanNotifaction("Error en la respuesta del servidor");
200
+ return false;
201
+ }
202
+ }
203
+
204
+ async function setUpFormAction() {
205
+ const saveWords = await sendWordsToSave();
206
+ if (!saveWords) {
207
+ return false;
208
+ }
209
+
210
+ const input = FORM_ACTION.querySelector(".action-input");
211
+ FORM_ACTION.action = "";
212
+ input.value = "finish_session";
213
+ FORM_ACTION.submit();
214
+ }
215
+
216
+ window.addEventListener("DOMContentLoaded", async () => {
217
+ initWordsFromBox();
218
+ setupDescribeFormToAddWord();
219
+
220
+ const currentPhase = parseInt(
221
+ document.querySelector(".cts-phase-pf").dataset.phase
222
+ );
223
+
224
+ if (currentPhase == 2) await sendWordsToSave();
225
+ if (document.querySelector(".cts-content-list-words"))
226
+ await getListWordsTesters();
227
+ });
tecnicas/static/js/test-cata.js CHANGED
@@ -100,8 +100,6 @@ document.addEventListener("DOMContentLoaded", () => {
100
  const result = await response.json();
101
  const messError = result.error;
102
 
103
- console.log(messError);
104
-
105
  if (messError) {
106
  message.textContent = messError;
107
  return;
 
100
  const result = await response.json();
101
  const messError = result.error;
102
 
 
 
103
  if (messError) {
104
  message.textContent = messError;
105
  return;
tecnicas/templates/tecnicas/components/form-scale-continue.html CHANGED
@@ -6,10 +6,11 @@
6
  class="text-xl font-bold tracking-wide block mb-6 first-letter:uppercase">{{ word }}</label>
7
 
8
  <span class="hidden id-word">{{ word.id }}</span>
9
-
10
  <section class="block">
11
  <div class="relative mx-6">
12
- <input type="range" type="range" min="0" max="1000" value="500" name="rating-word"
 
13
  class="range range-md text-blue-400 [--range-bg:orange] [--range-thumb:blue] [--range-fill:0]"
14
  style="width: {{scale.longitud}}cm;">
15
 
 
6
  class="text-xl font-bold tracking-wide block mb-6 first-letter:uppercase">{{ word }}</label>
7
 
8
  <span class="hidden id-word">{{ word.id }}</span>
9
+
10
  <section class="block">
11
  <div class="relative mx-6">
12
+ <input type="range" type="range" min="0" max="{{ size_scale.max_size }}"
13
+ value="{{ size_scale.middle_size }}" name="rating-word"
14
  class="range range-md text-blue-400 [--range-bg:orange] [--range-thumb:blue] [--range-fill:0]"
15
  style="width: {{scale.longitud}}cm;">
16
 
tecnicas/templates/tecnicas/components/item-list-words.html ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ <div class="cts-item-list-tester bg-surface-sweet px-4 pt-2 pb-3 rounded shrink-0 w-64 space-y-2">
2
+ <p class="bg-surface-card text-lg font-semibold text-center rounded">{{ list_tester.username }}</p>
3
+ <ul class="text-center grid grid-cols-2 gap-2 w-full">
4
+ {% for word in list_tester.words %}
5
+ <li class="bg-surface-ligt rounded font-bold py-1 px-2 truncate">{{ word.nombre_palabra }}</li>
6
+ {% endfor %}
7
+ </ul>
8
+ </div>
tecnicas/templates/tecnicas/components/table_pf.html ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% load static %}
2
+
3
+ <section data-tester="{{ data.tester }}" {% if session_name %} data-session-name="{{ session_name }}" {% endif %}
4
+ data-session-code="{{ session_code }}">
5
+ <h3 class="text-xl font-bold">Datos de usuario {{ data.tester }}</h3>
6
+
7
+ <div class="overflow-x-auto rounded-lg border border-surface-general">
8
+ <table class="convencional-table min-w-max w-full text-sm text-center border-collapse">
9
+ <thead class="bg-surface-sweet text-black font-semibold">
10
+ <tr>
11
+ <th class="py-2 px-3 border border-surface-general">Repetición</th>
12
+ <th class="py-2 px-3 border border-surface-general">Producto</th>
13
+ {% for word in data.words %}
14
+ <th class="py-2 px-3 border border-surface-general uppercase">{{ word.nombre_palabra }}</th>
15
+ {% endfor %}
16
+ </tr>
17
+ </thead>
18
+ <tbody class="bg-surface-ligt divide-y divide-gray-200">
19
+ {% for repeticion, data_product in data.ratings.items %}
20
+ {% for codigo, valores in data_product.items %}
21
+ <tr>
22
+ <td class="py-2 px-3 border border-surface-general">{{ repeticion }}</td>
23
+ <td class="py-2 px-3 border border-surface-general">{{ codigo }}</td>
24
+ {% for word in data.words %}
25
+ <td class="py-2 px-3 border border-surface-general">
26
+ {% for valor in valores %}
27
+ {% if valor.nombre_palabra == word.nombre_palabra %}
28
+ {{ valor.dato_valor }}
29
+ {% endif %}
30
+ {% endfor %}
31
+ </td>
32
+ {% endfor %}
33
+ </tr>
34
+ {% endfor %}
35
+ {% endfor %}
36
+ </tbody>
37
+ </table>
38
+ </div>
39
+ </section>
tecnicas/templates/tecnicas/forms_tester/convencional.html CHANGED
@@ -104,7 +104,7 @@
104
  {% with path_con="../components/form-scale-continue.html" path_str="../components/form-scale-structure.html" %}
105
  {% if type_scale == "continua" %}
106
  {% for word in words %}
107
- {% include path_con with word=word tags=tags scale=scale id_tecnica=session.tecnica.id %}
108
  {% endfor %}
109
  {% elif type_scale == "estructurada" %}
110
  {% for word in words %}
 
104
  {% with path_con="../components/form-scale-continue.html" path_str="../components/form-scale-structure.html" %}
105
  {% if type_scale == "continua" %}
106
  {% for word in words %}
107
+ {% include path_con with word=word tags=tags scale=scale size_scale=size_scale %}
108
  {% endfor %}
109
  {% elif type_scale == "estructurada" %}
110
  {% for word in words %}
tecnicas/templates/tecnicas/forms_tester/init_session_pf.html ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'tecnicas/layouts/base.html' %}
2
+ {% load static %}
3
+
4
+ {% block title %}Detalles Sesion{% endblock %}
5
+
6
+ {% block content %}
7
+ <article class="cts-container-main">
8
+ <article class="cts-wrap-content text-black max-w-4xl">
9
+ <header class="text-center flex-row w-full flex justify-around items-center flex-wrap gap-10">
10
+ <h1 class="rounded-xl font-bold text-2xl bg-surface-ligt p-4 flex-1">
11
+ Panel principal de Catadores
12
+ </h1>
13
+ </header>
14
+
15
+ <article>
16
+ <p class="text-2xl font-medium text-center bg-surface-sweet p-4 rounded-lg">
17
+ Información sobre la sesión en la que participa
18
+ </p>
19
+ </article>
20
+
21
+ {% if error %}
22
+ {% include "../components/error-message.html" with message=error %}
23
+ {% endif %}
24
+ {% if message %}
25
+ {% include "../components/error-message.html" with message=message %}
26
+ {% endif %}
27
+ <hr>
28
+
29
+ <article
30
+ class="rounded-xl grid grid-cols-1 gap-3 text-center *:bg-surface-card *:flex *:flex-wrap *:items-center *:justify-center *:gap-x-2 *:p-4 *:rounded-2xl">
31
+ <section>
32
+ <p class="text-xl font-bold">
33
+ Código:
34
+ </p>
35
+ <p class="font-sans text-xl font-normal">
36
+ {{ session.codigo_sesion }}
37
+ </p>
38
+ </section>
39
+
40
+ <section>
41
+ <p class="text-xl font-bold">
42
+ {% if session.nombre_sesion %}
43
+ {{ session.nombre_sesion }}
44
+ {% else %}
45
+ Sin nombre
46
+ {% endif %}
47
+ </p>
48
+ </section>
49
+
50
+ <section>
51
+ <p class="text-xl font-medium">
52
+ Esta sesión usa la técnica <span class="uppercase">{{ session.tecnica.tipo_tecnica }}</span>
53
+ </p>
54
+ </section>
55
+
56
+ <section>
57
+ <p class="text-xl font-medium">
58
+ <b>Actividad:</b> {{ activity }}
59
+ </p>
60
+ </section>
61
+
62
+ {% if repetition >= 1 %}
63
+ <section>
64
+ <p class="text-xl font-medium">
65
+ Repetición de la calificación: {{ repetition }}
66
+ </p>
67
+ </section>
68
+ {% endif %}
69
+
70
+ <section>
71
+ <p class="text-xl">
72
+ {{ session.tecnica.instrucciones }}
73
+ </p>
74
+ </section>
75
+ </article>
76
+
77
+ <hr>
78
+
79
+ <article class="flex flex-wrap gap-10">
80
+ {% if has_ended %}
81
+ {% if repetition >= 1 %}
82
+ <div
83
+ class="text-2xl font-semibold flex-1 cts-btn-secondary p-4 flex justify-center items-center rounded-lg select-none">
84
+ <p class=" text-black">Finalizaste la repetición</p>
85
+ </div>
86
+ {% else %}
87
+ <div
88
+ class="text-2xl font-semibold flex-1 cts-btn-secondary p-4 flex justify-center items-center rounded-lg select-none">
89
+ <p class=" text-black">Finalizaste la actividad</p>
90
+ </div>
91
+ {% endif %}
92
+ {% else %}
93
+ <button
94
+ class="ct-btn-start-repition flex-1 uppercase text-lg tracking-wider cts-btn-general cts-btn-primary btn-push flex flex-col justify-center items-center gap-2"
95
+ onclick="startTest()">
96
+ Entrar en fase o repetición
97
+ <figure class="w-10">
98
+ <img src="{% static 'img/check.svg' %}" alt="flechas girando" class="invert">
99
+ </figure>
100
+ </button>
101
+ {% endif %}
102
+ <a href="{% url 'cata_system:catador_list_sessions' num_page=1 %}">
103
+ <button
104
+ class="flex-1 uppercase text-lg tracking-wider cts-btn-general cts-btn-error btn-push flex flex-col justify-center items-center gap-2">
105
+ Regresar a la lista
106
+ <figure class="w-10">
107
+ <img src="{% static 'img/exit.svg' %}" alt="bote de basura" class="invert">
108
+ </figure>
109
+ </button>
110
+ </a>
111
+ </article>
112
+
113
+ <form action="" method="post" class="hidden ct-action-form">
114
+ {% csrf_token %}
115
+ <input type="hidden" class="action-option" name="action" value="start_posting">
116
+ </form>
117
+ </article>
118
+ </article>
119
+ {% endblock %}
120
+
121
+ {% block extra_js %}
122
+ <script src="{% static 'js/start-tester-test.js' %}"></script>
123
+ {% endblock %}
tecnicas/templates/tecnicas/forms_tester/test_pf_list_words.html ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'tecnicas/layouts/base.html' %}
2
+
3
+ {% load static %}
4
+
5
+ {% block title %}Convencional{% endblock %}
6
+
7
+ {% block content %}
8
+ <article class="cts-container-main">
9
+ <article class="cts-wrap-content text-black max-w-4xl">
10
+ <header class="text-center flex-row max-sm:flex-col w-full items-stretch flex justify-around flex-wrap gap-2">
11
+ <h1 class="rounded font-bold text-2xl bg-surface-ligt p-4 flex-1">
12
+ Sesión usando <br>técnica
13
+ <span class="uppercase">{{ session.tecnica.tipo_tecnica }}</span>
14
+ </h1>
15
+ <button class="cts-btn-general cts-btn-error btn-push" onclick="exit_sesion('form-actions')">
16
+ Salir de la sesión
17
+ </button>
18
+ </header>
19
+
20
+ <article class="hidden">
21
+ <form action="{% url 'cata_system:catador_init_session' code_sesion=session.codigo_sesion %}" method="post"
22
+ class="form-actions">
23
+ {% csrf_token %}
24
+ <input type="hidden" name="action" class="action-input">
25
+ </form>
26
+ </article>
27
+
28
+ <section class="hidden">
29
+ <input type="hidden" value="{{ session.tecnica.id }}" name="id-tecnica" class="ct-input-id-tech">
30
+ </section>
31
+
32
+ {% if error %}
33
+ {% include "../components/error-message.html" with message=error %}
34
+ {% endif %}
35
+
36
+ {% if not initial_phase %}
37
+ <div role="alert" class="alert alert-info">
38
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
39
+ class="h-6 w-6 shrink-0 stroke-current">
40
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
41
+ d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
42
+ </svg>
43
+ <span class="text-lg">
44
+ Para poder compartir tu nueva lista de palabras con los demás Catadores, debes usar el botón de
45
+ <b>“Guardar y comprobar palabras actuales”</b>
46
+ </span>
47
+ </div>
48
+ {% endif %}
49
+
50
+ <article class="rounded flex flex-col gap-4">
51
+ <section class="flex items-center justify-center bg-surface-ligt p-2 rounded-lg">
52
+ <p class="text-lg font-medium text-center">
53
+ {{ session.tecnica.instrucciones }}
54
+ </p>
55
+ </section>
56
+ <section class="flex items-center justify-center flex-wrap gap-4 bg-surface-ligt p-2 rounded-lg">
57
+ {% if initial_phase %}
58
+ <p class="text-xl font-bold text-center">
59
+ Fase <span class="cts-phase-pf" data-phase="1">1</span>:
60
+ </p>
61
+ <p class="text-xl font-bold text-center">
62
+ Lista inicial
63
+ </p>
64
+ {% else %}
65
+ <p class="text-xl font-bold text-center">
66
+ Fase <span class="cts-phase-pf" data-phase="2">2</span>:
67
+ </p>
68
+ <p class="text-xl font-bold text-center">
69
+ Lista Final
70
+ </p>
71
+ {% endif %}
72
+ </section>
73
+ </article>
74
+
75
+ <div class="error-input-word toast toast-bottom toast-center"></div>
76
+
77
+ <section class="cts-list-words space-y-4">
78
+ <article class="flex gap-4 flex-wrap">
79
+ <section class="bg-surface-card p-4 rounded-lg text-center space-y-4 sm:w-[300px] w-full flex-shrink-0">
80
+ <h3 class="text-xl font-bold border-b-1">Describe el producto</h3>
81
+
82
+ <form action="" method="get" class="cts-form-pf-word flex gap-4 flex-col">
83
+ <label for="{{ form.nombre_palabra.id_for_label }}" class="text-left">
84
+ <p class="text-lg font-semibold">
85
+ {{ form.nombre_palabra.label }}:
86
+ </p>
87
+ {{ form.nombre_palabra }}
88
+ <small>
89
+ Solo letras minúsculas y la letra ñ, no poner acentos. No se permiten mayúsculas ni
90
+ caracteres especiales. Mínimo 3 letras
91
+ </small>
92
+ </label>
93
+ <button type="submit" class="cts-btn-general-compress cts-btn-primary btn-push w-full py-2">
94
+ Agregar
95
+ </button>
96
+ </form>
97
+ </section>
98
+
99
+ <section
100
+ class="bg-surface-card p-4 rounded-lg text-center flex-1 h-[400px] min-h-[400px] max-h-[600px] flex-shrink-0 overflow-y-auto">
101
+ <div>
102
+ <p class="text-xl font-bold pb-3">Lista de palabras</p>
103
+ <hr>
104
+ </div>
105
+
106
+ <ul class="cts-box-words flex flex-col gap-3 overflow-y-auto mt-4">
107
+ {% if not words %}
108
+ <img src="{% static 'img/list.svg' %}" alt="lista de palabras" class="cts-img-list h-[300px]">
109
+ {% endif %}
110
+
111
+ {% for word in words %}
112
+ <img src="{% static 'img/list.svg' %}" alt="lista de palabras"
113
+ class="cts-img-list h-[300px] hidden">
114
+
115
+ <li class="cts-item-words bg-gray-400 text-black rounded font-bold text-lg
116
+ px-4 py-3 flex flex-wrap flex-row min-w-fit justify-center items-center gap-3">
117
+ <p class="ct-word-received">{{ word.nombre_palabra }}</p>
118
+
119
+ <button class="px-4 border-b-2 active:border-b-0 active:border-t-2
120
+ transition-all rounded-xl font-black capitalize
121
+ active:border-red-500 border-red-800 bg-red-500">
122
+
123
+ </button>
124
+ </li>
125
+ {% endfor %}
126
+ </ul>
127
+ </section>
128
+ </article>
129
+ </section>
130
+
131
+ <article class="flex max-sm:flex-col gap-4">
132
+ <button type="button" class="cts-btn-general-compress cts-btn-primary btn-push py-2 px-4 flex-1"
133
+ onclick="sendWordsToSave()">
134
+ Guardar y comprobar palabras actuales
135
+ </button>
136
+
137
+ <button type="button" class="cts-btn-general cts-btn-secondary btn-push py-2 px-4 flex-1"
138
+ onclick="setUpFormAction()">
139
+ Finalizar la sesión
140
+ </button>
141
+ </article>
142
+
143
+ <div role="alert" class="alert alert-warning">
144
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none"
145
+ viewBox="0 0 24 24">
146
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
147
+ 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" />
148
+ </svg>
149
+ <div class="flex-col">
150
+ <span class="text-lg block">
151
+ Si usas el botón <b>“Salir de la sesión”</b> asegúrese de guardar las palabras de lo contrario las
152
+ palabras no se guardaran pero podrás ingresar otra vez
153
+ </span>
154
+ <span class="text-lg block">
155
+ Con el botón <b>“Finalizar la sesión”</b>, se guardan las palabras, sales de la sesión y no podrás
156
+ ingresar otra vez
157
+ </span>
158
+ </div>
159
+ </div>
160
+
161
+ {% if not initial_phase %}
162
+ <section class="space-y-4">
163
+ <section class="flex items-center justify-center flex-wrap gap-4 bg-surface-ligt p-2 rounded-lg">
164
+ <h3 class="text-xl font-bold text-center">
165
+ Listas de los demás participantes
166
+ </h3>
167
+ </section>
168
+ <article class="cts-content-list-words bg-surface-card flex gap-4 overflow-x-auto p-2"></article>
169
+ <article class="flex justify-center">
170
+ <button class="cts-btn-general cts-btn-primary btn-push" onclick="getListWordsTesters()">
171
+ Actualizar las listas
172
+ </button>
173
+ </article>
174
+ </section>
175
+ <script src="{% static 'js/lists-words-testers.js' %}"></script>
176
+ {% endif %}
177
+ </article>
178
+ </article>
179
+ {% endblock %}
180
+
181
+ {% block extra_js %}
182
+ <script src="{% static 'js/pf-make-list.js' %}"></script>
183
+ <script src="{% static 'js/actions-form.js' %}"></script>
184
+ {% endblock %}
tecnicas/templates/tecnicas/forms_tester/test_pf_rating_list.html ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'tecnicas/layouts/base.html' %}
2
+
3
+ {% load static %}
4
+
5
+ {% block title %}Convencional{% endblock %}
6
+
7
+ {% block extra_css %}
8
+ <style>
9
+ input[type="range"]::-webkit-slider-thumb {
10
+ -webkit-appearance: none;
11
+ appearance: none;
12
+ width: 24px;
13
+ height: 24px;
14
+ border-radius: 50%;
15
+ background: var(--color-blue-500);
16
+ cursor: pointer;
17
+ }
18
+
19
+ input[type="range"]::-moz-range-thumb {
20
+ width: 24px;
21
+ height: 24px;
22
+ border-radius: 50%;
23
+ background: #3b82f6;
24
+ cursor: pointer;
25
+ }
26
+
27
+ @media (width < 40rem) {
28
+ .container-forms {
29
+ width: inherit;
30
+ }
31
+ }
32
+ </style>
33
+ {% endblock %}
34
+
35
+ {% block content %}
36
+ <article class="cts-container-main">
37
+ <article class="cts-wrap-content text-black">
38
+ <header class="text-center flex-row w-full items-stretch flex justify-around flex-wrap gap-2">
39
+ <h1 class="rounded font-bold text-2xl bg-surface-ligt p-4 flex-1">
40
+ Sesión usando <br>técnica
41
+ <span class="uppercase">{{ session.tecnica.tipo_tecnica }}</span>
42
+ </h1>
43
+ <button class="cts-btn-general cts-btn-error btn-push" onclick="exit_sesion('form-actions')">
44
+ Salir de la sesión
45
+ </button>
46
+ </header>
47
+
48
+ <article class="hidden">
49
+ <form action="{% url 'cata_system:catador_init_session' code_sesion=session.codigo_sesion %}" method="post"
50
+ class="form-actions">
51
+ {% csrf_token %}
52
+ <input type="hidden" name="action" class="action-input">
53
+ </form>
54
+ </article>
55
+
56
+ <section class="hidden">
57
+ <input type="hidden" value="{{ session.tecnica.id }}" name="id-tecnica" class="ct-input-id-tech">
58
+ </section>
59
+
60
+ {% if error %}
61
+ {% include "../components/error-message.html" with message=error %}
62
+ {% endif %}
63
+
64
+ <article class="rounded flex flex-col gap-4">
65
+ <section class="flex items-center justify-center bg-surface-ligt p-2 rounded-lg">
66
+ <p class="text-lg font-medium text-center">
67
+ {{ session.tecnica.instrucciones }}
68
+ </p>
69
+ </section>
70
+
71
+ <section class="flex items-center justify-center flex-wrap gap-4 bg-surface-ligt p-2 rounded-lg">
72
+ <p class="text-xl font-bold text-center">
73
+ Fase <span class="cts-phase-pf" data-phase="3">3</span>:
74
+ </p>
75
+ <p class="text-xl font-bold text-center">
76
+ Calificación de listas
77
+ </p>
78
+ </section>
79
+
80
+ <section class="flex items-center justify-center flex-wrap gap-4">
81
+ <div class="bg-surface-ligt p-2 rounded-lg flex-1">
82
+ <p class="text-lg font-bold text-center">
83
+ Producto:
84
+ </p>
85
+ <p class="text-2xl font-bold text-center ct-product-rating">
86
+ <span class="code-product">{{ product }}</span>
87
+ <span class="hidden id-product">{{ product.id }}</span>
88
+ </p>
89
+ </div>
90
+ <div class="bg-surface-ligt p-2 rounded-lg flex-1">
91
+ <p class="text-lg font-bold text-center">
92
+ Repetición:
93
+ </p>
94
+ <p class="text-2xl font-bold text-center">
95
+ {{ repetition }}
96
+ </p>
97
+ </div>
98
+ </section>
99
+
100
+ <section class="flex flex-col bg-red-200 py-2 px-4 rounded-lg min-sm:hidden">
101
+ <p>
102
+ El largo de estas escalas puede afectar la vista en dispositivos móviles. Para ver el resto de la
103
+ escala puede deslizar con el dedo a la derecha o la izquierda sin tocar la escala.
104
+ </p>
105
+ </section>
106
+ </article>
107
+
108
+ <article
109
+ class="scales-container [&>*:not(:last-child)]:mb-5 min-lg:grid min-lg:items-start grid-cols-2 gap-3 justify-center items-center">
110
+ {% with path_con="../components/form-scale-continue.html" path_str="../components/form-scale-structure.html" %}
111
+ {% if type_scale == "continua" %}
112
+ {% for word in words %}
113
+ {% include path_con with word=word tags=tags scale=scale size_scale=size_scale %}
114
+ {% endfor %}
115
+ {% elif type_scale == "estructurada" %}
116
+ {% for word in words %}
117
+ {% include path_str with word=word tags=tags scale=scale %}
118
+ {% endfor %}
119
+ {% endif %}
120
+ {% endwith %}
121
+ </article>
122
+ </article>
123
+ </article>
124
+ {% endblock %}
125
+
126
+ {% block extra_js %}
127
+ <script src="{% static 'js/created-scale.js' %}"></script>
128
+ <script src="{% static 'js/actions-form.js' %}"></script>
129
+ {% endblock %}
tecnicas/templates/tecnicas/manage_sesions/details-session-pf.html CHANGED
@@ -166,21 +166,72 @@
166
  Datos obtenidos
167
  </p>
168
 
169
- <article class="bg-surface-card p-4 max-sm:px-2 text-black rounded">
170
- <section class="text-xl space-y-4 space-x-4 mb-4">
171
- <p class="inline italic underline font-semibold">
172
- Se usa una escala <b>{{ tipo_escala }}</b> para calificar
173
- </p>
174
- <p class="inline italic underline font-semibold">
175
- El valor máximo por calificación es <b>{{ valor_max }}</b>
176
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  </section>
178
- {% if existen_calificaciones %}
179
- {% include "../components/table_cata.html" with calificaciones=calificaciones palabras=palabras
180
- sesion=sesion %}
181
- {% else %}
182
- {% include "../components/error-message.html" with message='Sin calificaciones que mostrar aún' %}
183
- {% endif %}
184
  </article>
185
 
186
  <form action="" method="post" class="form-action-session hidden">
@@ -192,6 +243,9 @@
192
  {% endblock %}
193
 
194
  {% block extra_js %}
 
 
 
195
  <script src="{% static 'js/details-session.js' %}"></script>
196
  <script src="{% static 'js/showHiddenElement.js' %}"></script>
197
  {% endblock %}
 
166
  Datos obtenidos
167
  </p>
168
 
169
+ <article class="bg-surface-card p-4 max-sm:px-2 text-black rounded space-y-4">
170
+ <section class="">
171
+ <header class="text-center">
172
+ <h3 class="text-xl font-bold">Prmera fase</h3>
173
+ <p class="text-lg">Listas iniciales de Catadores</p>
174
+ </header>
175
+
176
+ {% if fisrt_phase %}
177
+ <article class="flex gap-4 overflow-x-auto py-2">
178
+ {% for list_tester in fisrt_phase %}
179
+ {% include "../components/item-list-words.html" with list_tester=list_tester %}
180
+ {% endfor %}
181
+ </article>
182
+ {% else %}
183
+ {% include "../components/error-message.html" with message='Sin datos por mostrar aún' %}
184
+ {% endif %}
185
+ </section>
186
+
187
+
188
+ <section class="">
189
+ <header class="text-center">
190
+ <h3 class="text-xl font-bold">Segunda fase</h3>
191
+ <p class="text-lg">Listas finales de Catadores</p>
192
+ </header>
193
+
194
+ {% if second_phase %}
195
+ <article class="flex gap-4 overflow-x-auto py-2">
196
+ {% for list_tester in second_phase %}
197
+ {% include "../components/item-list-words.html" with list_tester=list_tester %}
198
+ {% endfor %}
199
+ </article>
200
+ {% else %}
201
+ {% include "../components/error-message.html" with message='Sin datos por mostrar aún' %}
202
+ {% endif %}
203
+ </section>
204
+
205
+ <section class="">
206
+ <header class="text-center">
207
+ <h3 class="text-xl font-bold">Tercera fase</h3>
208
+ <p class="text-lg">Listas finales de Catadores</p>
209
+ </header>
210
+
211
+ <article class="text-xl space-y-4 space-x-4 mb-4">
212
+ <p class="inline italic underline font-semibold">
213
+ Se usa una escala <b>{{ tipo_escala }}</b> para calificar
214
+ </p>
215
+ <p class="inline italic underline font-semibold">
216
+ El valor máximo por calificación es <b>{{ valor_max }}</b>
217
+ </p>
218
+ </article>
219
+
220
+ {% if data_ratings %}
221
+ <article class="overflow-x-auto py-2 space-y-4">
222
+ {% for data_tester in data_ratings %}
223
+ {% include "../components/table_pf.html" with data=data_tester session_name=sesion.nombre_sesion session_code=sesion.codigo_sesion %}
224
+ {% endfor %}
225
+ </article>
226
+ <div class="flex justify-end mt-3">
227
+ <button id="download-csv-btn" class="cts-btn-general cts-btn-primary btn-push">
228
+ Descargar datos como CSV en zip
229
+ </button>
230
+ </div>
231
+ {% else %}
232
+ {% include "../components/error-message.html" with message='Sin datos por mostrar aún' %}
233
+ {% endif %}
234
  </section>
 
 
 
 
 
 
235
  </article>
236
 
237
  <form action="" method="post" class="form-action-session hidden">
 
243
  {% endblock %}
244
 
245
  {% block extra_js %}
246
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
247
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
248
+ <script src="{% static 'js/download-table-zip.js' %}"></script>
249
  <script src="{% static 'js/details-session.js' %}"></script>
250
  <script src="{% static 'js/showHiddenElement.js' %}"></script>
251
  {% endblock %}
tecnicas/templates/tecnicas/manage_sesions/monitor-session-pf.html ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'tecnicas/layouts/base.html' %}
2
+ {% load static %}
3
+
4
+ {% block title %}Monitoreo{% endblock %}
5
+
6
+ {% block content %}
7
+ <article class="cts-container-main">
8
+ <article class="cts-wrap-content">
9
+ <header class="text-center">
10
+ <h1 class="text-3xl font-bold text-black">Sesión sensorial en curso</h1>
11
+ </header>
12
+
13
+ <section class="flex flex-col sm:flex-row justify-between items-center gap-10">
14
+ <p class="text-xl text-center bg-surface-card p-4 text-black rounded-lg shadow-lg">
15
+ Código de sesión:<br>
16
+ <span class="font-mono text-2xl font-bold">{{ code_session }}</span>
17
+ </p>
18
+ <div class="flex flex-col gap-2">
19
+ <button class="uppercase cts-btn-general-compress cts-btn-secondary py-2 px-6 btn-push"
20
+ onclick="finishSession()">
21
+ Finalizar sesión
22
+ </button>
23
+ <a href="{% url 'cata_system:detalles_sesion' session_code=code_session %}" class="w-full">
24
+ <button class="uppercase cts-btn-general-compress cts-btn-error py-2 px-6 btn-push w-full">
25
+ Regresar
26
+ </button>
27
+ </a>
28
+ </div>
29
+ </section>
30
+
31
+ <form action="" method="post" class="hidden action-form">
32
+ <input type="hidden" name="action">
33
+ {% csrf_token %}
34
+ </form>
35
+
36
+ {% if error %}
37
+ {% include "../components/error-message.html" with message=error %}
38
+ {% endif %}
39
+
40
+ {% if message %}
41
+ {% include "../components/error-message.html" with message=message %}
42
+ {% endif %}
43
+
44
+ <section aria-labelledby="catadores-titulo">
45
+ <article
46
+ class="flex max-sm:flex-col justify-around bg-surface-card border border-gray-300 rounded-md p-3 mb-4 items-center shadow-lg">
47
+ <button class="uppercase cts-btn-general-compress cts-btn-tertiary py-2 px-6 btn-push"
48
+ onclick="reloadPage()">
49
+ Actualizar lista
50
+ </button>
51
+ <h2 id="catadores-titulo" class="text-2xl text-center font-bold rounded-lg text-gray-700 py-2 px-3">
52
+ Catadores activos
53
+ </h2>
54
+ </article>
55
+
56
+ <article
57
+ class="flex flex-col justify-center gap-3 bg-surface-card border border-gray-300 rounded-md p-3 mb-4 text-lg max-sm:text-sm text-gray-700 shadow-lg">
58
+ <div class="flex justify-center items-center">
59
+ <h2 class="text-xl font-semibold">Participantes</h2>
60
+ </div>
61
+ <section class="flex flex-wrap justify-around items-center">
62
+ <div class="flex flex-col sm:flex-row sm:items-center sm:gap-2">
63
+ <span class="font-semibold">Máximo:</span>
64
+ <span>{{ max_testers }}</span>
65
+ </div>
66
+ <div class="flex flex-col sm:flex-row sm:items-center sm:gap-2">
67
+ <span class="font-semibold">
68
+ Actuales:
69
+ </span>
70
+ <span>{{ current_testers }}</span>
71
+ </div>
72
+ <div class="flex flex-col sm:flex-row sm:items-center sm:gap-2">
73
+ <span class="font-semibold">Activos:</span>
74
+ <span class="text-green-600 font-semibold">{{ active_testers }}</span>
75
+ </div>
76
+ </section>
77
+ </article>
78
+
79
+ <article class="max-sm:overflow-x-auto shadow-lg">
80
+ <div class="min-w-[400px] sm:min-w-0">
81
+ <div
82
+ class="grid grid-cols-4 bg-surface-sweet text-center font-semibold text-black text-sm rounded-t-lg max-sm:w-full [&_>*]:px-2">
83
+ <div class="py-2 border-r border-gray-400">Usuario</div>
84
+ <div class="py-2 border-r border-gray-400">Nombre</div>
85
+ <div class="py-2 border-r border-gray-400">Estado</div>
86
+ <div class="py-2">Finalizado</div>
87
+ </div>
88
+
89
+ <ul class="divide-y divide-gray-400 max-sm:w-full text-black">
90
+ {% for parti in participations %}
91
+ <li class="grid grid-cols-4 text-center bg-surface-card py-2 [&_>*]:px-2">
92
+ <p class="border-r border-gray-400">{{ parti.catador.user.username }}</p>
93
+ <p class="border-r border-gray-400">
94
+ {{ parti.catador.user.first_name }}
95
+ {{ parti.catador.user.last_name}}
96
+ </p>
97
+
98
+ {% if parti.activo %}
99
+ <p class="border-r border-gray-400 text-green-600 font-semibold">Activo</p>
100
+ {% else %}
101
+ <p class="border-r border-gray-400 text-red-500 font-semibold">No activo</p>
102
+ {% endif %}
103
+
104
+ {% if parti.finalizado %}
105
+ <p class="text-green-600 font-semibold">Si</p>
106
+ {% else %}
107
+ <p class="text-red-500 font-semibold">No</p>
108
+ {% endif %}
109
+ </li>
110
+ {% endfor %}
111
+ </ul>
112
+ </div>
113
+ </article>
114
+ </section>
115
+ </article>
116
+ </article>
117
+ {% endblock %}
118
+
119
+ {% block extra_js %}
120
+ <script>
121
+ function reloadPage () {
122
+ location.reload()
123
+ }
124
+ </script>
125
+ <script src="{% static 'js/finish-session.js' %}"></script>
126
+ {% endblock %}
tecnicas/urls.py CHANGED
@@ -114,6 +114,10 @@ urlpatterns = [
114
  views.cataTest,
115
  name="session_cata"),
116
 
 
 
 
 
117
 
118
  # APIs
119
  path("presenter/api/nueva-etiqueta",
@@ -135,4 +139,8 @@ urlpatterns = [
135
  path("testers/api/ratingword/cata",
136
  views.ratingWordCata,
137
  name="api_rating_word_cata"),
 
 
 
 
138
  ]
 
114
  views.cataTest,
115
  name="session_cata"),
116
 
117
+ path("testers/init-session/<str:code_sesion>/perfil-flash",
118
+ views.pfTest,
119
+ name="session_pf"),
120
+
121
 
122
  # APIs
123
  path("presenter/api/nueva-etiqueta",
 
139
  path("testers/api/ratingword/cata",
140
  views.ratingWordCata,
141
  name="api_rating_word_cata"),
142
+
143
+ path("testers/api/ratingword/pf/list",
144
+ views.apiListWordsPF,
145
+ name="api_rating_word_pf_list"),
146
  ]
tecnicas/views/__init__.py CHANGED
@@ -25,6 +25,7 @@ from .vocabulary_management.list_vocabulary import listVocabulary
25
  from .apis.api_tag import newTag
26
  from .apis.api_words import words
27
  from .apis.api_words import wordsVocabulary
 
28
  from .apis.rating_word_scales import ratingWordScales
29
  from .apis.rating_word_cata import ratingWordCata
30
 
@@ -34,3 +35,4 @@ from .tester_forms.login_session import loginSessionTester
34
  from .tester_forms.sessions_list_tester import sessionsListTester
35
  from .tester_forms.convencional_scales import convencionalScales
36
  from .tester_forms.cata_test import cataTest
 
 
25
  from .apis.api_tag import newTag
26
  from .apis.api_words import words
27
  from .apis.api_words import wordsVocabulary
28
+ from .apis.api_list_words_pf import apiListWordsPF
29
  from .apis.rating_word_scales import ratingWordScales
30
  from .apis.rating_word_cata import ratingWordCata
31
 
 
35
  from .tester_forms.sessions_list_tester import sessionsListTester
36
  from .tester_forms.convencional_scales import convencionalScales
37
  from .tester_forms.cata_test import cataTest
38
+ from .tester_forms.pf_test import pfTest
tecnicas/views/apis/api_list_words_pf.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.http import HttpRequest, JsonResponse
2
+ from tecnicas.utils import general_error
3
+ from tecnicas.controllers import RatingPFListController
4
+ import json
5
+
6
+
7
+ def apiListWordsPF(req: HttpRequest):
8
+ if req.method == "GET":
9
+ return RatingPFListController.getListWords(request=req)
10
+ elif req.method == "POST":
11
+ try:
12
+ data = json.loads(req.body.decode("utf-8"))
13
+ raw_words = data.get("words", [])
14
+ phase = data.get("phase", [])
15
+
16
+ response = RatingPFListController.saveList(
17
+ request=req, current_phase=phase, words=raw_words)
18
+ return response
19
+ except Exception as e:
20
+ print("Error:", e)
21
+ return JsonResponse({"error": "Error procesando datos"}, status=400)
22
+ else:
23
+ return JsonResponse({"error": "Método no permitido"}, status=405)
tecnicas/views/apis/rating_word_cata.py CHANGED
@@ -14,10 +14,8 @@ def ratingWordCata(req: HttpRequest):
14
  response = RatingCataController.saveRatingWords(
15
  request=req, data_words=raw_words, data_prodct=raw_product)
16
  return response
17
- return JsonResponse({"message": "Error procesando datos"})
18
  except Exception as e:
19
  print("Error:", e)
20
  return JsonResponse({"error": "Error procesando datos"}, status=400)
21
-
22
  else:
23
  return JsonResponse({"error": "Método no permitido"}, status=405)
 
14
  response = RatingCataController.saveRatingWords(
15
  request=req, data_words=raw_words, data_prodct=raw_product)
16
  return response
 
17
  except Exception as e:
18
  print("Error:", e)
19
  return JsonResponse({"error": "Error procesando datos"}, status=400)
 
20
  else:
21
  return JsonResponse({"error": "Método no permitido"}, status=405)
tecnicas/views/sessions_management/session_details.py CHANGED
@@ -55,16 +55,21 @@ def sessionDetails(req: HttpRequest, session_code: str):
55
  if req.POST["action"] == "start_session":
56
  response = controller_view.startRepetition(
57
  presenter=req.user.user_presentador, request=req)
 
58
  elif req.POST.get("action") == "delete_session":
59
  controller_view.deleteSesorialSession()
60
  response = redirect(
61
  reverse("cata_system:panel_sesiones", kwargs={"page": 1}))
 
62
  else:
63
  response = controller_view.getResponse(
64
  error="No se reconoce la acción a realizar")
 
65
  elif use_techinique == "perfil flash":
66
- response = JsonResponse(
67
- {"message": "Estoy trabajando en la funcionalidad que eligio"})
 
 
68
  else:
69
  response = noValidTechnique()
70
 
 
55
  if req.POST["action"] == "start_session":
56
  response = controller_view.startRepetition(
57
  presenter=req.user.user_presentador, request=req)
58
+
59
  elif req.POST.get("action") == "delete_session":
60
  controller_view.deleteSesorialSession()
61
  response = redirect(
62
  reverse("cata_system:panel_sesiones", kwargs={"page": 1}))
63
+
64
  else:
65
  response = controller_view.getResponse(
66
  error="No se reconoce la acción a realizar")
67
+
68
  elif use_techinique == "perfil flash":
69
+ controller_view = DetallesPFController(session=sensorial_session)
70
+ response = controller_view.startRepetition(
71
+ presenter=req.user.user_presentador, request=req)
72
+
73
  else:
74
  response = noValidTechnique()
75
 
tecnicas/views/sessions_management/session_monitor.py CHANGED
@@ -3,12 +3,11 @@ Para finalizar la sesion se debe realizar lo siguiente
3
  # Obtener todas las participaciones
4
 
5
  '''
6
-
7
  from django.http import HttpRequest, JsonResponse
8
  from django.shortcuts import render, redirect
9
  from django.urls import reverse
10
  from tecnicas.models import SesionSensorial
11
- from tecnicas.controllers import MonitorEscalasController, MonitorRATAController
12
  from tecnicas.utils import noValidTechnique
13
 
14
 
@@ -21,6 +20,11 @@ def sessionMonitor(req: HttpRequest, session_code: str):
21
  if use_techinique == "escalas" or use_techinique == "rata" or use_techinique == "cata":
22
  controll_view = MonitorEscalasController(sensorial_session)
23
  response = controll_view.controllGetResponse(request=req)
 
 
 
 
 
24
  else:
25
  response = noValidTechnique(
26
  params={
@@ -47,6 +51,7 @@ def sessionMonitor(req: HttpRequest, session_code: str):
47
  else:
48
  response = controll_view.controlGetResponse(
49
  request=req, error="No se ha definido la acción a realizar")
 
50
  elif use_techinique == "rata" or use_techinique == "cata":
51
  controll_view = MonitorRATAController(sensorial_session)
52
  action = req.POST["action"]
@@ -57,6 +62,18 @@ def sessionMonitor(req: HttpRequest, session_code: str):
57
  else:
58
  response = controll_view.controlGetResponse(
59
  request=req, error="No se ha definido la acción a realizar")
 
 
 
 
 
 
 
 
 
 
 
 
60
  else:
61
  response = noValidTechnique(
62
  params={
 
3
  # Obtener todas las participaciones
4
 
5
  '''
 
6
  from django.http import HttpRequest, JsonResponse
7
  from django.shortcuts import render, redirect
8
  from django.urls import reverse
9
  from tecnicas.models import SesionSensorial
10
+ from tecnicas.controllers import MonitorEscalasController, MonitorRATAController, MonitorPFController
11
  from tecnicas.utils import noValidTechnique
12
 
13
 
 
20
  if use_techinique == "escalas" or use_techinique == "rata" or use_techinique == "cata":
21
  controll_view = MonitorEscalasController(sensorial_session)
22
  response = controll_view.controllGetResponse(request=req)
23
+
24
+ elif use_techinique == "perfil flash":
25
+ controll_view = MonitorPFController(sensorial_session)
26
+ response = controll_view.controllGetResponse(request=req)
27
+
28
  else:
29
  response = noValidTechnique(
30
  params={
 
51
  else:
52
  response = controll_view.controlGetResponse(
53
  request=req, error="No se ha definido la acción a realizar")
54
+
55
  elif use_techinique == "rata" or use_techinique == "cata":
56
  controll_view = MonitorRATAController(sensorial_session)
57
  action = req.POST["action"]
 
62
  else:
63
  response = controll_view.controlGetResponse(
64
  request=req, error="No se ha definido la acción a realizar")
65
+
66
+ elif use_techinique == "perfil flash":
67
+ controll_view = MonitorPFController(sensorial_session)
68
+ action = req.POST["action"]
69
+
70
+ if action == "finish_session":
71
+ response = controll_view.controllPostFinishSession(
72
+ request=req)
73
+ else:
74
+ response = controll_view.controlGetResponse(
75
+ request=req, error="No se ha definido la acción a realizar")
76
+
77
  else:
78
  response = noValidTechnique(
79
  params={
tecnicas/views/tester_forms/cata_test.py CHANGED
@@ -1,7 +1,6 @@
1
  from django.http import HttpRequest
2
  from tecnicas.models import SesionSensorial
3
  from tecnicas.controllers import TestCataController
4
- from tecnicas.utils import noValidTechnique
5
 
6
 
7
  def cataTest(req: HttpRequest, code_sesion: str):
 
1
  from django.http import HttpRequest
2
  from tecnicas.models import SesionSensorial
3
  from tecnicas.controllers import TestCataController
 
4
 
5
 
6
  def cataTest(req: HttpRequest, code_sesion: str):
tecnicas/views/tester_forms/init_tester_form.py CHANGED
@@ -1,7 +1,6 @@
1
  from django.http import HttpRequest, JsonResponse
2
- from django.shortcuts import render, redirect
3
- from django.urls import reverse
4
- from tecnicas.controllers import InitSessionTesterController, ParticipacionController
5
  from tecnicas.models import SesionSensorial
6
 
7
 
@@ -10,14 +9,22 @@ def initTesterForm(req: HttpRequest, code_sesion: str):
10
  type_technique = session.tecnica.tipo_tecnica.nombre_tecnica
11
  template_url = "tecnicas/forms_tester/init_session.html"
12
 
13
- view_controller = InitSessionTesterController(
14
- sensorial_session=session, user_tester=req.user.user_catador)
15
-
16
  if req.method == "GET":
17
  if type_technique == "escalas":
18
- response = view_controller.controllGetEscalas(request=req)
 
 
 
19
  elif type_technique == "rata" or type_technique == "cata":
20
- response = view_controller.controllGetRATA(request=req)
 
 
 
 
 
 
 
 
21
  else:
22
  context = {
23
  "session": session,
@@ -27,10 +34,17 @@ def initTesterForm(req: HttpRequest, code_sesion: str):
27
  req, template_url, context)
28
 
29
  return response
 
30
  elif req.method == "POST":
31
  if type_technique == "escalas" or type_technique == "rata" or type_technique == "cata":
32
- response = view_controller.controllPostEscalas(request=req)
 
 
33
 
 
 
 
 
34
  else:
35
  context = {
36
  "session": session,
 
1
  from django.http import HttpRequest, JsonResponse
2
+ from django.shortcuts import render
3
+ from tecnicas.controllers import InitSessionEscalasController, InitSessionRATAController, InitSessionPFController
 
4
  from tecnicas.models import SesionSensorial
5
 
6
 
 
9
  type_technique = session.tecnica.tipo_tecnica.nombre_tecnica
10
  template_url = "tecnicas/forms_tester/init_session.html"
11
 
 
 
 
12
  if req.method == "GET":
13
  if type_technique == "escalas":
14
+ view_controller = InitSessionEscalasController(
15
+ sensorial_session=session, user_tester=req.user.user_catador)
16
+ response = view_controller.controllGet(request=req)
17
+
18
  elif type_technique == "rata" or type_technique == "cata":
19
+ view_controller = InitSessionRATAController(
20
+ sensorial_session=session, user_tester=req.user.user_catador)
21
+ response = view_controller.controllGet(request=req)
22
+
23
+ elif type_technique == "perfil flash":
24
+ view_controller = InitSessionPFController(
25
+ sensorial_session=session, user_tester=req.user.user_catador)
26
+ response = view_controller.controllGet(request=req)
27
+
28
  else:
29
  context = {
30
  "session": session,
 
34
  req, template_url, context)
35
 
36
  return response
37
+
38
  elif req.method == "POST":
39
  if type_technique == "escalas" or type_technique == "rata" or type_technique == "cata":
40
+ view_controller = InitSessionEscalasController(
41
+ sensorial_session=session, user_tester=req.user.user_catador)
42
+ response = view_controller.controllPost(request=req)
43
 
44
+ elif type_technique == "perfil flash":
45
+ view_controller = InitSessionPFController(
46
+ sensorial_session=session, user_tester=req.user.user_catador)
47
+ response = view_controller.controllPost(request=req)
48
  else:
49
  context = {
50
  "session": session,
tecnicas/views/tester_forms/login_session.py CHANGED
@@ -29,8 +29,12 @@ def loginSessionTester(req: HttpRequest):
29
 
30
  if type_technique == "escalas":
31
  response = login_controller.validateEntryEscalas(request=req)
 
32
  elif type_technique == "rata" or type_technique == "cata":
33
  response = login_controller.validateEntryRATA(request=req)
 
 
 
34
  else:
35
  context = {
36
  "error": "La técnica usada en esta sesión es invalida o no ha sido implementada para ingresar a ella"
 
29
 
30
  if type_technique == "escalas":
31
  response = login_controller.validateEntryEscalas(request=req)
32
+
33
  elif type_technique == "rata" or type_technique == "cata":
34
  response = login_controller.validateEntryRATA(request=req)
35
+
36
+ elif type_technique == "perfil flash":
37
+ response = login_controller.validateEntryRATA(request=req)
38
  else:
39
  context = {
40
  "error": "La técnica usada en esta sesión es invalida o no ha sido implementada para ingresar a ella"
tecnicas/views/tester_forms/pf_test.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.http import HttpRequest, JsonResponse
2
+ from django.shortcuts import redirect
3
+ from django.urls import reverse
4
+ from tecnicas.models import SesionSensorial
5
+ from tecnicas.controllers import TestPFController, ParticipacionController
6
+
7
+
8
+ def pfTest(req: HttpRequest, code_sesion: str):
9
+ if req.method == "GET":
10
+ session = SesionSensorial.objects.get(codigo_sesion=code_sesion)
11
+ controll_view = TestPFController(
12
+ sensorial_session=session, user_tester=req.user.user_catador)
13
+ return controll_view.controllGet(request=req)
14
+
15
+ if req.method == "POST":
16
+ session = SesionSensorial.objects.get(codigo_sesion=code_sesion)
17
+ controll_view = TestPFController(
18
+ sensorial_session=session, user_tester=req.user.user_catador)
19
+ return controll_view.controllPost(request=req)