Norberto Montalvo García commited on
Commit
e353854
·
unverified ·
2 Parent(s): 5c615df 3365a5c

Merge pull request #39 from CascoArcilla/HU7

Browse files
Files changed (36) hide show
  1. tecnicas/admin.py +7 -15
  2. tecnicas/controllers/__init__.py +6 -0
  3. tecnicas/controllers/api_controller/rating_napping_controller.py +96 -0
  4. tecnicas/controllers/views_controller/session_management/details/details_napping_controller.py +126 -0
  5. tecnicas/controllers/views_controller/session_management/monitor/monitor_napping_controller.py +37 -0
  6. tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_napping_controller.py +74 -0
  7. tecnicas/controllers/views_controller/sessions_tester/login_session_tester_controller.py +51 -37
  8. tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_napping_controller.py +74 -0
  9. tecnicas/migrations/0025_modalidad_alter_sesionsensorial_codigo_sesion_and_more.py +36 -0
  10. tecnicas/migrations/0026_tecnica_modalidad_and_more.py +27 -0
  11. tecnicas/migrations/0027_alter_sesionsensorial_codigo_sesion_and_more.py +24 -0
  12. tecnicas/migrations/0028_alter_sesionsensorial_codigo_sesion_and_more.py +24 -0
  13. tecnicas/migrations/0029_remove_tecnica_modalidad_and_more.py +23 -0
  14. tecnicas/migrations/0030_alter_sesionsensorial_codigo_sesion_tecnicamodalidad.py +29 -0
  15. tecnicas/migrations/0031_calificacion_palabras_and_more.py +34 -0
  16. tecnicas/models/__init__.py +3 -0
  17. tecnicas/models/calificacion.py +12 -4
  18. tecnicas/models/dato_punto.py +12 -0
  19. tecnicas/models/modalidad.py +8 -0
  20. tecnicas/models/tecnica.py +1 -0
  21. tecnicas/models/tecnica_modalidad.py +12 -0
  22. tecnicas/static/js/actions-form.js +9 -1
  23. tecnicas/static/js/details-session.js +9 -1
  24. tecnicas/static/js/test-napping.js +209 -0
  25. tecnicas/templates/tecnicas/components/table-napping-no-mode.html +41 -0
  26. tecnicas/templates/tecnicas/forms_tester/init_test_napping.html +113 -0
  27. tecnicas/templates/tecnicas/forms_tester/test_napping.html +150 -0
  28. tecnicas/templates/tecnicas/manage_sesions/details-session-napping.html +198 -0
  29. tecnicas/urls.py +8 -0
  30. tecnicas/views/__init__.py +2 -0
  31. tecnicas/views/apis/rating_napping.py +16 -0
  32. tecnicas/views/sessions_management/session_details.py +19 -4
  33. tecnicas/views/sessions_management/session_monitor.py +16 -1
  34. tecnicas/views/tester_forms/init_tester_form.py +14 -4
  35. tecnicas/views/tester_forms/login_session.py +6 -9
  36. tecnicas/views/tester_forms/napping_test.py +27 -0
tecnicas/admin.py CHANGED
@@ -1,20 +1,6 @@
1
  from django.contrib import admin
2
 
3
- from .models import CategoriaTecnica, TipoTecnica, TipoEscala, EstiloPalabra
4
-
5
- from .models import Catador, Presentador
6
-
7
- from .models import Tecnica, SesionSensorial
8
-
9
- from .models import EsAtributo, Palabra, Vocabulario
10
-
11
- from .models import Etiqueta, Escala, EtiquetasEscala
12
-
13
- from .models import Producto, Participacion
14
-
15
- from .models import Orden, Posicion
16
-
17
- from .models import Dato, ValorDecimal, ValorBooleano, Calificacion, ListaPalabras, GrupoProducto
18
 
19
  # Register your models here.
20
  admin.site.register(CategoriaTecnica)
@@ -46,5 +32,11 @@ admin.site.register(Dato)
46
  admin.site.register(ValorDecimal)
47
  admin.site.register(ValorBooleano)
48
  admin.site.register(Calificacion)
 
49
  admin.site.register(ListaPalabras)
50
  admin.site.register(GrupoProducto)
 
 
 
 
 
 
1
  from django.contrib import admin
2
 
3
+ from .models import CategoriaTecnica, TipoTecnica, TipoEscala, EstiloPalabra, Catador, Presentador, Tecnica, SesionSensorial, EsAtributo, Palabra, Vocabulario, Etiqueta, Escala, EtiquetasEscala, Producto, Participacion, Orden, Posicion, Dato, ValorDecimal, ValorBooleano, Calificacion, ListaPalabras, GrupoProducto, Modalidad, TecnicaModalidad, DatoPunto
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
  # Register your models here.
6
  admin.site.register(CategoriaTecnica)
 
32
  admin.site.register(ValorDecimal)
33
  admin.site.register(ValorBooleano)
34
  admin.site.register(Calificacion)
35
+
36
  admin.site.register(ListaPalabras)
37
  admin.site.register(GrupoProducto)
38
+
39
+ admin.site.register(Modalidad)
40
+ admin.site.register(TecnicaModalidad)
41
+
42
+ admin.site.register(DatoPunto)
tecnicas/controllers/__init__.py CHANGED
@@ -31,11 +31,13 @@ from .views_controller.session_management.details.details_rata_controller import
31
  from .views_controller.session_management.details.details_cata_controller import DetallesCATAController
32
  from .views_controller.session_management.details.details_pf_controller import DetallesPFController
33
  from .views_controller.session_management.details.details_sort_controller import DetallesSortController
 
34
 
35
  from .views_controller.session_management.monitor.monitor_escalas_controller import MonitorEscalasController
36
  from .views_controller.session_management.monitor.monitor_rata_controller import MonitorRATAController
37
  from .views_controller.session_management.monitor.monitor_pf_controller import MonitorPFController
38
  from .views_controller.session_management.monitor.monitor_sort_controller import MonitorSortController
 
39
 
40
  from .views_controller.sessions_tester.login_session_tester_controller import LoginSessionTesterController
41
  from .views_controller.sessions_tester.list_sessions_tester_controller import ListSessionsTesterController
@@ -45,11 +47,13 @@ from .views_controller.sessions_tester.tests_forms.test_rata_controller import T
45
  from .views_controller.sessions_tester.tests_forms.test_cata_controller import TestCataController
46
  from .views_controller.sessions_tester.tests_forms.test_pf_controller import TestPFController
47
  from .views_controller.sessions_tester.tests_forms.test_sort_controller import TestSortController
 
48
 
49
  from .views_controller.sessions_tester.init_session.init_session_escalas_controller import InitSessionEscalasController
50
  from .views_controller.sessions_tester.init_session.init_session_rata_controller import InitSessionRATAController
51
  from .views_controller.sessions_tester.init_session.init_session_pf_controller import InitSessionPFController
52
  from .views_controller.sessions_tester.init_session.init_session_sort_controller import InitSessionSortController
 
53
 
54
  from .views_controller.vocabulary_manage.create_vocabulary_controller import CreateVocabularyController
55
  from .views_controller.vocabulary_manage.list_vocabulary_controller import ListVocabularyController
@@ -59,3 +63,5 @@ from .api_controller.rating_cata_controller import RatingCataController
59
  from .api_controller.rating_pf_list_controller import RatingPFListController
60
  from .views_controller.tester_list_controller import TesterListController
61
  from .api_controller.rating_sort_controller import RatingSortController
 
 
 
31
  from .views_controller.session_management.details.details_cata_controller import DetallesCATAController
32
  from .views_controller.session_management.details.details_pf_controller import DetallesPFController
33
  from .views_controller.session_management.details.details_sort_controller import DetallesSortController
34
+ from .views_controller.session_management.details.details_napping_controller import DetallesNappingController
35
 
36
  from .views_controller.session_management.monitor.monitor_escalas_controller import MonitorEscalasController
37
  from .views_controller.session_management.monitor.monitor_rata_controller import MonitorRATAController
38
  from .views_controller.session_management.monitor.monitor_pf_controller import MonitorPFController
39
  from .views_controller.session_management.monitor.monitor_sort_controller import MonitorSortController
40
+ from .views_controller.session_management.monitor.monitor_napping_controller import MonitorNappingController
41
 
42
  from .views_controller.sessions_tester.login_session_tester_controller import LoginSessionTesterController
43
  from .views_controller.sessions_tester.list_sessions_tester_controller import ListSessionsTesterController
 
47
  from .views_controller.sessions_tester.tests_forms.test_cata_controller import TestCataController
48
  from .views_controller.sessions_tester.tests_forms.test_pf_controller import TestPFController
49
  from .views_controller.sessions_tester.tests_forms.test_sort_controller import TestSortController
50
+ from .views_controller.sessions_tester.tests_forms.test_napping_controller import TestNappingController
51
 
52
  from .views_controller.sessions_tester.init_session.init_session_escalas_controller import InitSessionEscalasController
53
  from .views_controller.sessions_tester.init_session.init_session_rata_controller import InitSessionRATAController
54
  from .views_controller.sessions_tester.init_session.init_session_pf_controller import InitSessionPFController
55
  from .views_controller.sessions_tester.init_session.init_session_sort_controller import InitSessionSortController
56
+ from .views_controller.sessions_tester.init_session.init_session_napping_controller import InitSessionNappingController
57
 
58
  from .views_controller.vocabulary_manage.create_vocabulary_controller import CreateVocabularyController
59
  from .views_controller.vocabulary_manage.list_vocabulary_controller import ListVocabularyController
 
63
  from .api_controller.rating_pf_list_controller import RatingPFListController
64
  from .views_controller.tester_list_controller import TesterListController
65
  from .api_controller.rating_sort_controller import RatingSortController
66
+ from .api_controller.rating_napping_controller import RatingNappingController
67
+
tecnicas/controllers/api_controller/rating_napping_controller.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.http import JsonResponse
2
+ from django.http import HttpRequest
3
+ from django.db import transaction
4
+ from tecnicas.models import Calificacion, DatoPunto, Producto, Participacion
5
+
6
+
7
+ class RatingNappingController:
8
+ @staticmethod
9
+ def saveRatingCoordinates(request: HttpRequest, data: list):
10
+ participation = Participacion.objects.get(
11
+ id=request.session["id_participation"]
12
+ )
13
+
14
+ try:
15
+ with transaction.atomic():
16
+ products_map = RatingNappingController.getProductsMap(
17
+ participation.tecnica)
18
+
19
+ existing_ratings_map = RatingNappingController.getExistingRatingsMap(
20
+ participation.tecnica, participation.catador
21
+ )
22
+
23
+ new_ratings = []
24
+ ids_products = products_map.keys()
25
+ for item in data:
26
+ product_id = int(item["idProduct"])
27
+ if product_id not in existing_ratings_map and product_id in ids_products:
28
+ new_ratings.append(
29
+ Calificacion(
30
+ num_repeticion=0,
31
+ id_producto=products_map[product_id],
32
+ id_tecnica=participation.tecnica,
33
+ id_catador=participation.catador,
34
+ )
35
+ )
36
+
37
+ if new_ratings:
38
+ Calificacion.objects.bulk_create(new_ratings)
39
+ existing_ratings_map = RatingNappingController.getExistingRatingsMap(
40
+ participation.tecnica, participation.catador
41
+ )
42
+
43
+ existing_points_map = RatingNappingController.getExistingPointsMap(
44
+ existing_ratings_map.values())
45
+
46
+ points_to_create = []
47
+ points_to_update = []
48
+
49
+ for item in data:
50
+ product_id = int(item["idProduct"])
51
+ rating = existing_ratings_map.get(product_id)
52
+
53
+ if rating:
54
+ if rating.id in existing_points_map:
55
+ point = existing_points_map[rating.id]
56
+ point.x = item["x"]
57
+ point.y = item["y"]
58
+ points_to_update.append(point)
59
+ else:
60
+ points_to_create.append(
61
+ DatoPunto(
62
+ x=item["x"],
63
+ y=item["y"],
64
+ calificacion=rating,
65
+ )
66
+ )
67
+
68
+ if points_to_create:
69
+ DatoPunto.objects.bulk_create(points_to_create)
70
+
71
+ if points_to_update:
72
+ DatoPunto.objects.bulk_update(points_to_update, ['x', 'y'])
73
+
74
+ return JsonResponse({"message": "Datos guardados exitosamente"})
75
+
76
+ except Exception as e:
77
+ print("ERROR:", e)
78
+ return JsonResponse({"error": "Error al procesar datos"})
79
+
80
+ @staticmethod
81
+ def getProductsMap(id_tecnica):
82
+ products_qs = Producto.objects.filter(id_tecnica=id_tecnica)
83
+ return {p.id: p for p in products_qs}
84
+
85
+ @staticmethod
86
+ def getExistingRatingsMap(id_tecnica, id_catador):
87
+ ratings = Calificacion.objects.filter(
88
+ id_tecnica=id_tecnica,
89
+ id_catador=id_catador,
90
+ )
91
+ return {r.id_producto.id: r for r in ratings}
92
+
93
+ @staticmethod
94
+ def getExistingPointsMap(ratings):
95
+ points = DatoPunto.objects.filter(calificacion__in=ratings)
96
+ return {p.calificacion.id: p for p in points}
tecnicas/controllers/views_controller/session_management/details/details_napping_controller.py ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.http import HttpRequest
2
+ from django.shortcuts import redirect
3
+ from django.urls import reverse
4
+ from django.db.models import F
5
+ from .details_controller import DetallesController
6
+ from tecnicas.models import SesionSensorial, Presentador, Modalidad, TecnicaModalidad, Catador, Participacion, DatoPunto, Calificacion
7
+ from tecnicas.utils import defaultdict_to_dict
8
+ from collections import defaultdict
9
+
10
+
11
+ class DetallesNappingController(DetallesController):
12
+ def __init__(self, session: SesionSensorial):
13
+ super().__init__(session)
14
+ self.url_template = "tecnicas/manage_sesions/details-session-napping.html"
15
+ self.url_next = "cata_system:monitor_sesion"
16
+
17
+ def getContext(self):
18
+ self.context = {
19
+ "session": self.session,
20
+ }
21
+
22
+ self.defineStatus()
23
+ self.setOptionesMode()
24
+ self.setDataTableNoMode()
25
+
26
+ return self.context
27
+
28
+ def defineStatus(self):
29
+ repetition = self.session.tecnica.repeticion
30
+
31
+ if not repetition and not self.session.activo:
32
+ self.context["status"] = "Listo para iniciar la sesión con Napping"
33
+ elif not repetition and self.session.activo:
34
+ self.context["status"] = "Sesión con Napping en curso"
35
+ elif repetition == 1 and not self.session.activo:
36
+ self.context["status"] = "En espera de la siguiente acción"
37
+ else:
38
+ self.context["status"] = "En espera de la siguiente acción"
39
+
40
+ def controllPostResponse(self, request: HttpRequest, action: str):
41
+ if action == "start_sin_modalidad":
42
+ name_mode = action.replace("start_", "").replace("_", " ")
43
+ response = self.startNapping(request=request, name_mode=name_mode)
44
+
45
+ if action == "start_perfil_ultra_flash":
46
+ name_mode = action.replace("start_", "").replace("_", " ")
47
+ return self.controllGetResponse(error="Trabajando en la modalidad", request=request)
48
+
49
+ elif action == "delete_session":
50
+ self.deleteSesorialSession()
51
+ response = redirect(
52
+ reverse("cata_system:panel_sesiones", kwargs={"page": 1}))
53
+
54
+ else:
55
+ response = self.controllGetResponse(
56
+ error="Modalidad sin implantar", request=request)
57
+
58
+ return response
59
+
60
+ def startNapping(self, request: HttpRequest, name_mode: str):
61
+ if request.user.user_presentador.user.username != self.session.creadoPor.user.username:
62
+ return self.controllGetResponse(error="Solo el presentador que crea la sesión puede iniciar la repetición", request=request)
63
+ elif self.session.activo:
64
+ return self.controllGetResponse(error="La sesión ya está activada", request=request)
65
+
66
+ tecnique_mode = TecnicaModalidad.objects.get_or_create(
67
+ tecnica=self.session.tecnica, modalidad=Modalidad.objects.get(nombre=name_mode), usando=True)
68
+
69
+ if not tecnique_mode:
70
+ return self.controllGetResponse(error="Modalidad no encontrada", request=request)
71
+
72
+ self.session.activo = True
73
+ self.session.save()
74
+
75
+ parameters = {
76
+ "session_code": self.session.codigo_sesion
77
+ }
78
+ return redirect(
79
+ reverse(self.url_next, kwargs=parameters))
80
+
81
+ def setDataTableNoMode(self):
82
+ participations = Participacion.objects.filter(
83
+ tecnica=self.session.tecnica).select_related("catador")
84
+ testers = [participation.catador for participation in participations]
85
+ self.context["testers"] = testers
86
+
87
+ ratings = Calificacion.objects.filter(id_tecnica=self.session.tecnica)
88
+
89
+ coordinates = (
90
+ DatoPunto.objects.filter(calificacion__in=ratings)
91
+ .values(
92
+ producto=F("calificacion__id_producto__codigoProducto"),
93
+ catador=F("calificacion__id_catador__user__username"),
94
+ px=F("x"),
95
+ py=F("y"),
96
+ ))
97
+
98
+ if not coordinates.exists():
99
+ self.context["there_data"] = False
100
+ return []
101
+
102
+ coordinates_by_product = defaultdict(dict)
103
+
104
+ for coordinate in coordinates:
105
+ coordinates_by_product[coordinate["producto"]][coordinate["catador"]] = {
106
+ "px": coordinate["px"],
107
+ "py": coordinate["py"],
108
+ }
109
+
110
+ self.context["coordinates_no_mode"] = defaultdict_to_dict(
111
+ coordinates_by_product)
112
+
113
+ self.context["there_data"] = True
114
+
115
+ def setOptionesMode(self):
116
+ modes = Modalidad.objects.all()
117
+ technique_modes = TecnicaModalidad.objects.filter(
118
+ tecnica=self.session.tecnica)
119
+
120
+ if not technique_modes.exists():
121
+ self.context["modes"] = modes
122
+ else:
123
+ use_modes = technique_modes.values_list("modalidad", flat=True)
124
+
125
+ self.context["modes"] = modes.exclude(
126
+ id__in=use_modes)
tecnicas/controllers/views_controller/session_management/monitor/monitor_napping_controller.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from tecnicas.models import SesionSensorial
2
+ from tecnicas.models import Participacion, TecnicaModalidad
3
+ from .monitor_controller import MonitorController
4
+
5
+
6
+ class MonitorNappingController(MonitorController):
7
+ def __init__(self, session: SesionSensorial):
8
+ super().__init__(session)
9
+ self.url_view = "tecnicas/manage_sesions/monitor-session-sort.html"
10
+ self.previus_view = "cata_system:detalles_sesion"
11
+
12
+ def checkAllFinish(self) -> (bool, str):
13
+ technique = self.sensorial_session.tecnica
14
+
15
+ num_participations = Participacion.objects.filter(
16
+ tecnica=technique).count()
17
+
18
+ if num_participations < technique.limite_catadores:
19
+ return (False, "No se ha alcanzado el número máximo de catadores")
20
+
21
+ unfinished_participations = Participacion.objects.filter(
22
+ tecnica=technique, finalizado=False).count()
23
+
24
+ if unfinished_participations > 0:
25
+ return (False, "No todos los catadores han finalizado su evaluación")
26
+
27
+ return (True, "Puedes finalizar la sesión")
28
+
29
+ def finishSession(self):
30
+ mode_technique = TecnicaModalidad.objects.get(
31
+ tecnica=self.sensorial_session.tecnica, usando=True)
32
+ mode_technique.usando = False
33
+ mode_technique.save()
34
+
35
+ self.sensorial_session.activo = False
36
+ self.sensorial_session.save()
37
+ return self.sensorial_session
tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_napping_controller.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
5
+ from tecnicas.controllers import ParticipacionController
6
+ from .init_session_controller import InitSessionController
7
+
8
+
9
+ class InitSessionNappingController(InitSessionController):
10
+ def __init__(self, sensorial_session, user_tester):
11
+ super().__init__(sensorial_session, user_tester)
12
+ self.current_direction = "tecnicas/forms_tester/init_test_napping.html"
13
+ self.napping_direction = "cata_system:session_napping"
14
+
15
+ def controllGet(self, request: HttpRequest):
16
+ self.context = {
17
+ "session": self.session,
18
+ "type_technique": "napping",
19
+ "has_ended": self.isEndedSession()
20
+ }
21
+
22
+ if self.session.tecnica.repeticion == 1:
23
+ self.context["status"] = "En esta sesión se usará Napping"
24
+ else:
25
+ self.context["status"] = "Se uso Napping puro en la última sesión"
26
+
27
+ if "error" in request.GET:
28
+ self.context["error"] = request.GET["error"]
29
+
30
+ return render(request, self.current_direction, self.context)
31
+
32
+ def isEndedSession(self):
33
+ return Participacion.objects.get(
34
+ tecnica=self.session.tecnica, catador=self.tester).finalizado
35
+
36
+ def controllPost(self, request: HttpRequest):
37
+ context = {
38
+ "session": self.session,
39
+ "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
40
+ }
41
+
42
+ use_action = request.POST["action"]
43
+
44
+ if use_action == "start_posting":
45
+ parameters = {
46
+ "code_sesion": self.session.codigo_sesion
47
+ }
48
+
49
+ is_end = self.isEndedSession()
50
+ if is_end:
51
+ context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
52
+ return render(request, self.current_direction, context)
53
+
54
+ update_participation = ParticipacionController.enterSession(
55
+ tester=request.user.user_catador, session=self.session)
56
+
57
+ if isinstance(update_participation, dict):
58
+ context["error"] = update_participation["error"]
59
+ return render(request, self.current_direction, context)
60
+
61
+ request.session["id_participation"] = update_participation.id
62
+
63
+ return redirect(reverse(self.napping_direction, kwargs=parameters))
64
+
65
+ elif use_action == "exit_session":
66
+ response = ParticipacionController.outSession(
67
+ tester=request.user.user_catador, session=self.session)
68
+ if isinstance(response, dict):
69
+ context["error"] = response["error"]
70
+ return render(request, self.current_direction, context)
71
+
72
+ else:
73
+ context["error"] = "Acción sin especificar"
74
+ return render(request, self.current_direction, context)
tecnicas/controllers/views_controller/sessions_tester/login_session_tester_controller.py CHANGED
@@ -39,6 +39,7 @@ class LoginSessionTesterController():
39
  tecnica=self.session.tecnica, catador=self.tester)
40
  context["error"] = "Usted ya esta dentro de la sesión"
41
  return render(request, self.current_direcction, context)
 
42
  except Participacion.DoesNotExist:
43
  with transaction.atomic():
44
  code_session = self.session.codigo_sesion
@@ -66,7 +67,7 @@ class LoginSessionTesterController():
66
  context["error"] = "Ya no es posible ingresar a la sesión"
67
  return render(request, self.current_direcction, context)
68
 
69
- def validateEntryRATA(self, request: HttpRequest):
70
  context = {}
71
  if not self.session.activo:
72
  context["error"] = "La sesión no está activa actualmente"
@@ -98,51 +99,64 @@ class LoginSessionTesterController():
98
  context["error"] = "Imposible acceder a esta sesión"
99
  return render(request, self.current_direcction, context)
100
 
101
- def validateEntrySort(self, request: HttpRequest):
102
  context = {}
103
  if not self.session.activo:
104
  context["error"] = "La sesión no está activa actualmente"
105
  return render(request, self.current_direcction, context)
106
 
107
  if self.session.tecnica.repeticion == 1:
108
- try:
109
- self.taster_participation = Participacion.objects.get(
110
- tecnica=self.session.tecnica, catador=self.tester)
111
- context["error"] = "Usted ya esta dentro de la sesión"
112
- return render(request, self.current_direcction, context)
113
 
114
- except Participacion.DoesNotExist:
115
- try:
116
- with transaction.atomic():
117
- code_session = self.session.codigo_sesion
118
- self.session = SesionSensorial.objects.select_for_update().get(
119
- codigo_sesion=code_session)
120
-
121
- max_testers = self.session.tecnica.limite_catadores
122
- current_num_testers = Participacion.objects.filter(
123
- tecnica=self.session.tecnica).count()
124
-
125
- if current_num_testers >= max_testers:
126
- raise ValueError(
127
- "La sesión ha alcanzado el número máximo de catadores")
128
-
129
- self.taster_participation = Participacion.objects.create(
130
- tecnica=self.session.tecnica,
131
- catador=self.tester,
132
- finalizado=False
133
- )
134
- params = {
135
- "code_sesion": self.session.codigo_sesion
136
- }
137
- return redirect(reverse(self.destinity_direcction, kwargs=params))
138
-
139
- except ValueError as e:
140
- context["error"] = str(e)
141
- return render(request, self.current_direcction, context)
142
 
143
  else:
144
  context["error"] = "Ya no es posible ingresar a la sesión"
145
  return render(request, self.current_direcction, context)
146
 
147
- def validateEntryPF(self, request=HttpRequest):
148
- return self.validateEntryEscalas(request=request)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  tecnica=self.session.tecnica, catador=self.tester)
40
  context["error"] = "Usted ya esta dentro de la sesión"
41
  return render(request, self.current_direcction, context)
42
+
43
  except Participacion.DoesNotExist:
44
  with transaction.atomic():
45
  code_session = self.session.codigo_sesion
 
67
  context["error"] = "Ya no es posible ingresar a la sesión"
68
  return render(request, self.current_direcction, context)
69
 
70
+ def validateEntryRataCata(self, request: HttpRequest):
71
  context = {}
72
  if not self.session.activo:
73
  context["error"] = "La sesión no está activa actualmente"
 
99
  context["error"] = "Imposible acceder a esta sesión"
100
  return render(request, self.current_direcction, context)
101
 
102
+ def validateEntryLimitTesters(self, request: HttpRequest):
103
  context = {}
104
  if not self.session.activo:
105
  context["error"] = "La sesión no está activa actualmente"
106
  return render(request, self.current_direcction, context)
107
 
108
  if self.session.tecnica.repeticion == 1:
109
+ return self.entrySessionLimitTesters(request)
 
 
 
 
110
 
111
+ else:
112
+ context["error"] = "Ya no es posible ingresar a la sesión"
113
+ return render(request, self.current_direcction, context)
114
+
115
+ def validateEntryNapping(self, request: HttpRequest):
116
+ context = {}
117
+ if not self.session.activo:
118
+ context["error"] = "La sesión no está activa actualmente"
119
+ return render(request, self.current_direcction, context)
120
+
121
+ if self.session.tecnica.repeticion == 0:
122
+ return self.entrySessionLimitTesters(request)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
  else:
125
  context["error"] = "Ya no es posible ingresar a la sesión"
126
  return render(request, self.current_direcction, context)
127
 
128
+ def entrySessionLimitTesters(self, request: HttpRequest):
129
+ try:
130
+ self.taster_participation = Participacion.objects.get(
131
+ tecnica=self.session.tecnica, catador=self.tester)
132
+ context["error"] = "Usted ya esta dentro de la sesión"
133
+ return render(request, self.current_direcction, context)
134
+
135
+ except Participacion.DoesNotExist:
136
+ try:
137
+ with transaction.atomic():
138
+ code_session = self.session.codigo_sesion
139
+ self.session = SesionSensorial.objects.select_for_update().get(
140
+ codigo_sesion=code_session)
141
+
142
+ max_testers = self.session.tecnica.limite_catadores
143
+ current_num_testers = Participacion.objects.filter(
144
+ tecnica=self.session.tecnica).count()
145
+
146
+ if current_num_testers >= max_testers:
147
+ raise ValueError(
148
+ "La sesión ha alcanzado el número máximo de catadores")
149
+
150
+ self.taster_participation = Participacion.objects.create(
151
+ tecnica=self.session.tecnica,
152
+ catador=self.tester,
153
+ finalizado=False
154
+ )
155
+ params = {
156
+ "code_sesion": self.session.codigo_sesion
157
+ }
158
+ return redirect(reverse(self.destinity_direcction, kwargs=params))
159
+
160
+ except ValueError as e:
161
+ context["error"] = str(e)
162
+ return render(request, self.current_direcction, context)
tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_napping_controller.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.http import HttpRequest
2
+ from django.shortcuts import redirect, render
3
+ from django.urls import reverse
4
+ from django.db.models import F
5
+ from tecnicas.models import Participacion, Producto, TecnicaModalidad, DatoPunto, Calificacion
6
+ from tecnicas.utils import noValidTechnique
7
+ from .general_test_controller import GenetalTestController
8
+
9
+
10
+ class TestNappingController(GenetalTestController):
11
+ def __init__(self, sensorial_session, user_tester):
12
+ super().__init__(sensorial_session, user_tester)
13
+ self.napping_test = "tecnicas/forms_tester/test_napping.html"
14
+
15
+ def controllGet(self, request: HttpRequest):
16
+ technique = self.session.tecnica
17
+
18
+ self.participation = Participacion.objects.get(
19
+ tecnica=technique, catador=request.user.user_catador)
20
+
21
+ # Comprobar que el Catador no haya finalizado
22
+ if self.participation.finalizado:
23
+ params = {
24
+ "code_sesion": self.session.codigo_sesion
25
+ }
26
+ return redirect(reverse(self.previus_directory, kwargs=params))
27
+
28
+ name_mode_activate = TecnicaModalidad.objects.get(
29
+ tecnica=technique, usando=True).modalidad.nombre
30
+
31
+ if name_mode_activate == "sin modalidad":
32
+ self.context["mode"] = "sin modalidad"
33
+ return self.nappingTest(request)
34
+ else:
35
+ return noValidTechnique(
36
+ name_view=self.previus_directory,
37
+ query_params={
38
+ "error": "La técnica no tiene modalidad activada"
39
+ },
40
+ params={
41
+ "code_sesion": self.session.codigo_sesion
42
+ }
43
+ )
44
+
45
+ def nappingTest(self, request: HttpRequest):
46
+ self.context["session"] = self.session
47
+ technique = self.session.tecnica
48
+
49
+ products_in_technique = Producto.objects.filter(id_tecnica=technique)
50
+ self.context["products"] = products_in_technique
51
+
52
+ self.setCoordinates()
53
+
54
+ return render(request, self.napping_test, self.context)
55
+
56
+ def setCoordinates(self):
57
+ technique = self.session.tecnica
58
+
59
+ ratings = Calificacion.objects.filter(
60
+ num_repeticion=0,
61
+ id_tecnica=technique,
62
+ id_catador=self.participation.catador
63
+ )
64
+
65
+ data_points = DatoPunto.objects.filter(
66
+ calificacion__in=ratings
67
+ ).values(
68
+ code= F("calificacion__id_producto__codigoProducto"),
69
+ px= F("x"),
70
+ py= F("y"),
71
+ id_product= F("calificacion__id_producto")
72
+ )
73
+
74
+ self.context["data_points"] = list(data_points)
tecnicas/migrations/0025_modalidad_alter_sesionsensorial_codigo_sesion_and_more.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 5.2.1 on 2025-11-28 19:09
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', '0024_alter_sesionsensorial_codigo_sesion_grupoproducto'),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.CreateModel(
16
+ name='Modalidad',
17
+ fields=[
18
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19
+ ('nombre', models.CharField(max_length=255)),
20
+ ],
21
+ ),
22
+ migrations.AlterField(
23
+ model_name='sesionsensorial',
24
+ name='codigo_sesion',
25
+ field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False),
26
+ ),
27
+ migrations.CreateModel(
28
+ name='TecnicaModalidad',
29
+ fields=[
30
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
31
+ ('usado', models.BooleanField(default=False)),
32
+ ('modalidad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='modalidad_tecnica', to='tecnicas.modalidad')),
33
+ ('tecnica', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tecnica_modalidad', to='tecnicas.tecnica')),
34
+ ],
35
+ ),
36
+ ]
tecnicas/migrations/0026_tecnica_modalidad_and_more.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 5.2.1 on 2025-11-28 19:39
2
+
3
+ import shortuuid.main
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('tecnicas', '0025_modalidad_alter_sesionsensorial_codigo_sesion_and_more'),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AddField(
15
+ model_name='tecnica',
16
+ name='modalidad',
17
+ field=models.ManyToManyField(related_name='modalidad_tecnica', to='tecnicas.modalidad'),
18
+ ),
19
+ migrations.AlterField(
20
+ model_name='sesionsensorial',
21
+ name='codigo_sesion',
22
+ field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False),
23
+ ),
24
+ migrations.DeleteModel(
25
+ name='TecnicaModalidad',
26
+ ),
27
+ ]
tecnicas/migrations/0027_alter_sesionsensorial_codigo_sesion_and_more.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 5.2.1 on 2025-11-28 19:59
2
+
3
+ import shortuuid.main
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('tecnicas', '0026_tecnica_modalidad_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
+ migrations.AlterField(
20
+ model_name='tecnica',
21
+ name='modalidad',
22
+ field=models.ManyToManyField(blank=True, null=True, related_name='modalidad_tecnica', to='tecnicas.modalidad'),
23
+ ),
24
+ ]
tecnicas/migrations/0028_alter_sesionsensorial_codigo_sesion_and_more.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 5.2.1 on 2025-11-28 20:10
2
+
3
+ import shortuuid.main
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('tecnicas', '0027_alter_sesionsensorial_codigo_sesion_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
+ migrations.AlterField(
20
+ model_name='tecnica',
21
+ name='modalidad',
22
+ field=models.ManyToManyField(blank=True, related_name='modalidad_tecnica', to='tecnicas.modalidad'),
23
+ ),
24
+ ]
tecnicas/migrations/0029_remove_tecnica_modalidad_and_more.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 5.2.1 on 2025-11-28 21:48
2
+
3
+ import shortuuid.main
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('tecnicas', '0028_alter_sesionsensorial_codigo_sesion_and_more'),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.RemoveField(
15
+ model_name='tecnica',
16
+ name='modalidad',
17
+ ),
18
+ migrations.AlterField(
19
+ model_name='sesionsensorial',
20
+ name='codigo_sesion',
21
+ field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False),
22
+ ),
23
+ ]
tecnicas/migrations/0030_alter_sesionsensorial_codigo_sesion_tecnicamodalidad.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 5.2.1 on 2025-11-28 21:54
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', '0029_remove_tecnica_modalidad_and_more'),
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='TecnicaModalidad',
22
+ fields=[
23
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
24
+ ('usando', models.BooleanField(default=False)),
25
+ ('modalidad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='modalidad_tecnica', to='tecnicas.modalidad')),
26
+ ('tecnica', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tecnica_modalidad', to='tecnicas.tecnica')),
27
+ ],
28
+ ),
29
+ ]
tecnicas/migrations/0031_calificacion_palabras_and_more.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 5.2.1 on 2025-11-29 03:05
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', '0030_alter_sesionsensorial_codigo_sesion_tecnicamodalidad'),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AddField(
16
+ model_name='calificacion',
17
+ name='palabras',
18
+ field=models.ManyToManyField(blank=True, related_name='calificacion_palabras', to='tecnicas.palabra'),
19
+ ),
20
+ migrations.AlterField(
21
+ model_name='sesionsensorial',
22
+ name='codigo_sesion',
23
+ field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False),
24
+ ),
25
+ migrations.CreateModel(
26
+ name='DatoPunto',
27
+ fields=[
28
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
29
+ ('x', models.FloatField(default=0)),
30
+ ('y', models.FloatField(default=0)),
31
+ ('calificacion', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='data_punto', to='tecnicas.calificacion')),
32
+ ],
33
+ ),
34
+ ]
tecnicas/models/__init__.py CHANGED
@@ -3,6 +3,7 @@ from .etiqueta import Etiqueta
3
  from .tipo_escala import TipoEscala
4
  from .tipo_tecnica import TipoTecnica
5
  from .estilo_palabra import EstiloPalabra
 
6
 
7
  from .presentador import Presentador
8
  from .catador import Catador
@@ -31,3 +32,5 @@ from .participacion import Participacion
31
 
32
  from .lista_palabras import ListaPalabras
33
  from .grupo_producto import GrupoProducto
 
 
 
3
  from .tipo_escala import TipoEscala
4
  from .tipo_tecnica import TipoTecnica
5
  from .estilo_palabra import EstiloPalabra
6
+ from .modalidad import Modalidad
7
 
8
  from .presentador import Presentador
9
  from .catador import Catador
 
32
 
33
  from .lista_palabras import ListaPalabras
34
  from .grupo_producto import GrupoProducto
35
+ from .tecnica_modalidad import TecnicaModalidad
36
+ from .dato_punto import DatoPunto
tecnicas/models/calificacion.py CHANGED
@@ -3,12 +3,20 @@ from django.db import models
3
  from .producto import Producto
4
  from .tecnica import Tecnica
5
  from .catador import Catador
 
 
6
 
7
  class Calificacion(models.Model):
8
  num_repeticion = models.IntegerField()
9
- id_producto = models.ForeignKey(Producto, on_delete=models.CASCADE, related_name="calificacion_producto")
10
- id_tecnica = models.ForeignKey(Tecnica, on_delete=models.CASCADE, related_name="calificacion_tecnica")
11
- id_catador = models.ForeignKey(Catador, on_delete=models.CASCADE, related_name="calificacion_catador")
 
 
 
 
 
 
12
 
13
  def __str__(self):
14
- return f"{self.id} - {self.id_tecnica.sesion_tecnica} - {self.num_repeticion} - {self.id_catador.user.username}"
 
3
  from .producto import Producto
4
  from .tecnica import Tecnica
5
  from .catador import Catador
6
+ from .palabra import Palabra
7
+
8
 
9
  class Calificacion(models.Model):
10
  num_repeticion = models.IntegerField()
11
+ id_producto = models.ForeignKey(
12
+ Producto, on_delete=models.CASCADE, related_name="calificacion_producto")
13
+ id_tecnica = models.ForeignKey(
14
+ Tecnica, on_delete=models.CASCADE, related_name="calificacion_tecnica")
15
+ id_catador = models.ForeignKey(
16
+ Catador, on_delete=models.CASCADE, related_name="calificacion_catador")
17
+
18
+ palabras = models.ManyToManyField(
19
+ Palabra, related_name="calificacion_palabras", blank=True)
20
 
21
  def __str__(self):
22
+ return f"{self.id} - {self.id_tecnica.sesion_tecnica} - {self.num_repeticion} - {self.id_catador.user.username}"
tecnicas/models/dato_punto.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.db import models
2
+ from .calificacion import Calificacion
3
+
4
+
5
+ class DatoPunto(models.Model):
6
+ x = models.FloatField(null=False, default=0)
7
+ y = models.FloatField(null=False, default=0)
8
+ calificacion = models.ForeignKey(
9
+ Calificacion, on_delete=models.CASCADE, related_name="data_punto")
10
+
11
+ def __str__(self):
12
+ return f"({self.calificacion.id_producto.codigoProducto}: {self.x}, {self.y})"
tecnicas/models/modalidad.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from django.db import models
2
+
3
+
4
+ class Modalidad(models.Model):
5
+ nombre = models.CharField(max_length=255)
6
+
7
+ def __str__(self):
8
+ return self.nombre
tecnicas/models/tecnica.py CHANGED
@@ -2,6 +2,7 @@ from django.db import models
2
 
3
  from .estilo_palabra import EstiloPalabra
4
  from .tipo_tecnica import TipoTecnica
 
5
 
6
 
7
  class Tecnica(models.Model):
 
2
 
3
  from .estilo_palabra import EstiloPalabra
4
  from .tipo_tecnica import TipoTecnica
5
+ from .modalidad import Modalidad
6
 
7
 
8
  class Tecnica(models.Model):
tecnicas/models/tecnica_modalidad.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.db import models
2
+ from .tecnica import Tecnica
3
+ from .modalidad import Modalidad
4
+
5
+
6
+ class TecnicaModalidad(models.Model):
7
+ tecnica = models.ForeignKey(
8
+ Tecnica, on_delete=models.CASCADE, related_name="tecnica_modalidad")
9
+ modalidad = models.ForeignKey(
10
+ Modalidad, on_delete=models.CASCADE, related_name="modalidad_tecnica")
11
+
12
+ usando = models.BooleanField(default=False)
tecnicas/static/js/actions-form.js CHANGED
@@ -3,4 +3,12 @@ function exit_sesion(styleClass) {
3
  const action = form.querySelector(".action-input");
4
  action.value = "exit_session";
5
  form.submit();
6
- }
 
 
 
 
 
 
 
 
 
3
  const action = form.querySelector(".action-input");
4
  action.value = "exit_session";
5
  form.submit();
6
+ }
7
+
8
+ function finishSession(styleClass) {
9
+ const form = document.querySelector(`.${styleClass}`);
10
+ form.action = ""
11
+ const action = form.querySelector(".action-input");
12
+ action.value = "finish_session";
13
+ form.submit();
14
+ }
tecnicas/static/js/details-session.js CHANGED
@@ -17,4 +17,12 @@ function deleteSession() {
17
  const input = actionForm.querySelector(".action-option")
18
  input.value = "delete_session";
19
  actionForm.submit();
20
- }
 
 
 
 
 
 
 
 
 
17
  const input = actionForm.querySelector(".action-option")
18
  input.value = "delete_session";
19
  actionForm.submit();
20
+ }
21
+
22
+ function startSession(nameMode) {
23
+ const nameUnderscort = nameMode.replaceAll(" ", "_");
24
+ console.log(nameUnderscort);
25
+ const input = actionForm.querySelector(".action-option")
26
+ input.value = `start_${nameUnderscort}`;
27
+ actionForm.submit();
28
+ }
tecnicas/static/js/test-napping.js ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const planeContainer = document.getElementById('napping-plane');
2
+ const productsContainer = document.getElementById('items');
3
+ const products = document.querySelectorAll('.item-product');
4
+
5
+ // Configuration for the physical dimensions of the tablecloth (in cm)
6
+ const PHYSICAL_WIDTH = 60;
7
+ const PHYSICAL_HEIGHT = 40;
8
+
9
+ let selectedProductCode = null;
10
+ let selectedProductId = null;
11
+
12
+ // Object to store coordinates: { "CODE": { x: 10.5, y: 20.1, id: 123 } }
13
+ const placedPoints = {};
14
+
15
+ // 1. Handle Product Selection
16
+ products.forEach(product => {
17
+ product.addEventListener('click', () => {
18
+ // Remove selection from others
19
+ products.forEach(p => p.classList.remove('ring-4', 'ring-primary'));
20
+
21
+ // Select current
22
+ product.classList.add('ring-4', 'ring-primary');
23
+ selectedProductCode = product.dataset.code;
24
+ selectedProductId = product.dataset.idProduct;
25
+ });
26
+ });
27
+
28
+ // 2. Handle Plane Click (Placing Points)
29
+ planeContainer.addEventListener('click', (e) => {
30
+ if (!selectedProductCode) {
31
+ spanNotifaction("Por favor, selecciona un producto primero")
32
+ return;
33
+ }
34
+
35
+ const rect = planeContainer.getBoundingClientRect();
36
+
37
+ // Calculate click position relative to the container (in pixels)
38
+ const xPixel = e.clientX - rect.left;
39
+ const yPixel = e.clientY - rect.top;
40
+
41
+ // Calculate scaled coordinates (0 to PHYSICAL_DIMENSIONS)
42
+ // X axis: 0 on left, 60 on right
43
+ const xCoord = (xPixel / rect.width) * PHYSICAL_WIDTH;
44
+
45
+ // Y axis: 0 on bottom, 40 on top (Invert Y because DOM Y is 0 at top)
46
+ const yCoord = PHYSICAL_HEIGHT - ((yPixel / rect.height) * PHYSICAL_HEIGHT);
47
+
48
+ // Update Data Object
49
+ placedPoints[selectedProductCode] = {
50
+ x: parseFloat(xCoord.toFixed(2)),
51
+ y: parseFloat(yCoord.toFixed(2)),
52
+ id: selectedProductId
53
+ };
54
+
55
+ // Render Point
56
+ renderPoint(selectedProductCode, xPixel, yPixel, xCoord, yCoord);
57
+ });
58
+
59
+ function renderPoint(code, xPx, yPx, xVal, yVal) {
60
+ // Remove existing point for this product if it exists
61
+ const existingPoint = document.getElementById(`point-${code}`);
62
+ if (existingPoint) {
63
+ existingPoint.remove();
64
+ }
65
+
66
+ const point = document.createElement('div');
67
+ point.id = `point-${code}`;
68
+ point.className = 'absolute w-4 h-4 bg-red-600 rounded-full transform -translate-x-1/2 -translate-y-1/2 cursor-pointer border-2 border-white shadow-md group';
69
+ point.style.left = `${xPx}px`;
70
+ point.style.top = `${yPx}px`;
71
+
72
+ // Tooltip/Label
73
+ const label = document.createElement('div');
74
+ label.className = 'absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-gray-800 text-white text-xs rounded whitespace-nowrap z-10 hidden group-hover:block';
75
+ label.innerHTML = `
76
+ <strong>${code}</strong><br>
77
+ X: ${xVal.toFixed(1)}<br>
78
+ Y: ${yVal.toFixed(1)}
79
+ `;
80
+
81
+ point.appendChild(label);
82
+ planeContainer.appendChild(point);
83
+
84
+ const textLabel = document.createElement('span');
85
+ textLabel.className = 'absolute top-4 left-1/2 transform -translate-x-1/2 text-xs font-bold text-gray-700 pointer-events-none';
86
+ textLabel.innerText = code;
87
+ point.appendChild(textLabel);
88
+ }
89
+
90
+ function checkPoints() {
91
+ const points = document.querySelectorAll('.data-point');
92
+
93
+ points.forEach(point => {
94
+ const code = point.dataset.code;
95
+
96
+ const xVal = parseFloat(point.dataset.px);
97
+ const yVal = parseFloat(point.dataset.py);
98
+
99
+ const rect = planeContainer.getBoundingClientRect();
100
+
101
+ const px = (xVal / PHYSICAL_WIDTH) * rect.width;
102
+ const py = ((PHYSICAL_HEIGHT - yVal) / PHYSICAL_HEIGHT) * rect.height;
103
+
104
+ placedPoints[code] = {
105
+ x: parseFloat(xVal.toFixed(2)),
106
+ y: parseFloat(yVal.toFixed(2)),
107
+ id: point.dataset.idProduct
108
+ };
109
+
110
+ renderPoint(code, px, py, xVal, yVal);
111
+ });
112
+ }
113
+
114
+ checkPoints();
115
+
116
+ /*
117
+ ////
118
+ //////
119
+ //////// Question to finish session
120
+ //////
121
+ ////
122
+ */
123
+
124
+ function showOptionsSave() {
125
+ document.getElementById("question-save").classList.add("hidden");
126
+ document.getElementById("finish-session").classList.remove("hidden");
127
+ document.getElementById("cancel-save").classList.remove("hidden");
128
+ }
129
+
130
+ function showQuestionSave() {
131
+ document.getElementById("question-save").classList.remove("hidden");
132
+ document.getElementById("finish-session").classList.add("hidden");
133
+ document.getElementById("cancel-save").classList.add("hidden");
134
+ }
135
+
136
+ document
137
+ .getElementById("question-save")
138
+ .addEventListener("click", showOptionsSave);
139
+
140
+ document
141
+ .getElementById("cancel-save")
142
+ .addEventListener("click", showQuestionSave);
143
+
144
+ /*
145
+ ////
146
+ //////
147
+ //////// Save data and finish session
148
+ //////
149
+ ////
150
+ */
151
+
152
+ async function saveData() {
153
+ const codeProducts = Object.keys(placedPoints);
154
+ const data = [];
155
+
156
+ if (products.length != codeProducts.length) {
157
+ spanNotifaction("Por favor, coloca todos los puntos")
158
+ return;
159
+ }
160
+
161
+ codeProducts.forEach((code) => {
162
+ const point = placedPoints[code];
163
+
164
+ const objData = {
165
+ code: code,
166
+ x: point.x,
167
+ y: point.y,
168
+ idProduct: point.id
169
+ };
170
+
171
+ data.push(objData);
172
+ })
173
+
174
+ const URL = "/cata/testers/api/rating-napping/no-mode"
175
+ const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
176
+
177
+ try {
178
+ const response = await fetch(URL, {
179
+ method: "POST",
180
+ headers: {
181
+ "Content-Type": "application/json",
182
+ "X-CSRFToken": csrfToken,
183
+ },
184
+ body: JSON.stringify(data),
185
+ })
186
+
187
+ if (!response.ok) {
188
+ spanNotifaction("Error en la respuesta del servidor")
189
+ return false;
190
+ }
191
+
192
+ const result = await response.json()
193
+
194
+ if (result.error) {
195
+ spanNotifaction(result.error)
196
+ return false
197
+ } else {
198
+ spanNotifaction(result.message, false)
199
+ return true
200
+ }
201
+ } catch (error) {
202
+ spanNotifaction("Error en proceso de guardar los datos")
203
+ return false
204
+ }
205
+ }
206
+
207
+ document
208
+ .getElementById("save-progress")
209
+ .addEventListener("click", saveData);
tecnicas/templates/tecnicas/components/table-napping-no-mode.html ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% load custom_filters %}
2
+ <article class="space-y-4 text-black">
3
+ <h2 class="font-bold text-xl text-center">
4
+ Datos de Napping sin modalidad
5
+ </h2>
6
+
7
+ <div class="overflow-x-auto rounded-lg border border-surface-general">
8
+ <table id="generic-donwload-table" class="min-w-max w-full text-sm text-center border-collapse">
9
+ <thead class="bg-surface-sweet font-semibold">
10
+ <tr>
11
+ <th class="py-2 px-3 border border-surface-general">Producto</th>
12
+ {% for tester in testers %}
13
+ <th class="py-2 px-3 border border-surface-general uppercase">
14
+ X{{ forloop.counter }}
15
+ </th>
16
+ <th class="py-2 px-3 border border-surface-general uppercase">
17
+ Y{{ forloop.counter }}
18
+ </th>
19
+ {% endfor %}
20
+ </tr>
21
+ </thead>
22
+ <tbody class="bg-surface-ligt divide-y divide-gray-200">
23
+ {% for product, coordinates_tester in coordinates_no_mode.items %}
24
+ <tr>
25
+ <td class="py-2 px-3 border border-surface-general">{{ product }}</td>
26
+ {% for tester in testers %}
27
+ {% with points=coordinates_tester|get_item:tester.user.username %}
28
+ <td class="py-2 px-3 border border-surface-general">
29
+ {{ points.px }}
30
+ </td>
31
+ <td class="py-2 px-3 border border-surface-general">
32
+ {{ points.py }}
33
+ </td>
34
+ {% endwith %}
35
+ {% endfor %}
36
+ </tr>
37
+ {% endfor %}
38
+ </tbody>
39
+ </table>
40
+ </div>
41
+ </article>
tecnicas/templates/tecnicas/forms_tester/init_test_napping.html ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 class="rounded-xl grid grid-cols-1 gap-3 text-center grid grid-cols-2">
30
+ <section class="bg-surface-card col-span-2 p-4 rounded-2xl flex max-sm:flex-col justify-center gap-2">
31
+ <p class="text-xl font-bold">
32
+ Código:
33
+ </p>
34
+ <p class="font-sans text-xl font-normal">
35
+ {{ session.codigo_sesion }}
36
+ </p>
37
+ </section>
38
+
39
+ <section class="bg-surface-card col-span-2 p-4 rounded-2xl">
40
+ <p class="text-xl font-bold">
41
+ {% if session.nombre_sesion %}
42
+ {{ session.nombre_sesion }}
43
+ {% else %}
44
+ Sin nombre
45
+ {% endif %}
46
+ </p>
47
+ </section>
48
+
49
+ <section class="bg-surface-card p-4 rounded-2xl flex max-sm:flex-col justify-center gap-2">
50
+ <p class="text-xl font-medium">
51
+ Esta sesión usa la técnica <span class="uppercase">{{ session.tecnica.tipo_tecnica }}</span>
52
+ </p>
53
+ </section>
54
+
55
+ <section class="bg-surface-card p-4 rounded-2xl flex max-sm:flex-col justify-center gap-2">
56
+ <p class="text-xl font-medium">
57
+ Sesión Única
58
+ </p>
59
+ </section>
60
+
61
+ <section class="bg-surface-card col-span-2 p-4 rounded-2xl">
62
+ <p class="text-xl">
63
+ {{ session.tecnica.instrucciones }}
64
+ </p>
65
+ </section>
66
+
67
+ <section class="bg-surface-card col-span-2 p-4 rounded-2xl">
68
+ <p class="text-xl">
69
+ {{ status }}
70
+ </p>
71
+ </section>
72
+ </article>
73
+
74
+ <hr>
75
+
76
+ <article class="flex flex-wrap gap-10">
77
+ {% if has_ended %}
78
+ <div
79
+ class="text-2xl font-semibold flex-1 cts-btn-secondary p-4 flex justify-center items-center rounded-lg select-none">
80
+ <p class=" text-black">Finalizaste la sesión</p>
81
+ </div>
82
+ {% else %}
83
+ <button
84
+ 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"
85
+ onclick="startTest()">
86
+ Iniciar técnica
87
+ <figure class="w-10">
88
+ <img src="{% static 'img/check.svg' %}" alt="flechas girando" class="invert">
89
+ </figure>
90
+ </button>
91
+ {% endif %}
92
+ <a href="{% url 'cata_system:catador_list_sessions' num_page=1 %}">
93
+ <button
94
+ 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">
95
+ Regresar a la lista
96
+ <figure class="w-10">
97
+ <img src="{% static 'img/exit.svg' %}" alt="bote de basura" class="invert">
98
+ </figure>
99
+ </button>
100
+ </a>
101
+ </article>
102
+
103
+ <form action="" method="post" class="hidden ct-action-form">
104
+ {% csrf_token %}
105
+ <input type="hidden" class="action-option" name="action" value="start_posting">
106
+ </form>
107
+ </article>
108
+ </article>
109
+ {% endblock %}
110
+
111
+ {% block extra_js %}
112
+ <script src="{% static 'js/start-tester-test.js' %}"></script>
113
+ {% endblock %}
tecnicas/templates/tecnicas/forms_tester/test_napping.html ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'tecnicas/layouts/base.html' %}
2
+
3
+ {% load static %}
4
+
5
+ {% block title %}Napping{% 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 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
+ <article class="rounded flex flex-col gap-4">
37
+ <section class="flex items-center justify-center bg-surface-ligt p-2 rounded-lg">
38
+ <p class="text-lg font-medium text-center">
39
+ {{ session.tecnica.instrucciones }}
40
+ </p>
41
+ </section>
42
+
43
+ <section class="flex items-center justify-center flex-wrap gap-4 bg-surface-ligt p-2 rounded-lg">
44
+ <p class="text-xl font-bold text-center capitalize">
45
+ Modalidad: {% if mode != "sin modalidad" %}{{ mode }} {% else %} Nappging {% endif %}
46
+ </p>
47
+ </section>
48
+ </article>
49
+
50
+ <article class="container-rating-word p-2 py-6 space-y-6 bg-surface-ligt rounded min-w-3xl">
51
+ <h2 class="text-xl font-bold">Productos en la sesión</h2>
52
+ <div id="items" class="original-products flex gap-4 flex-wrap justify-center"
53
+ data-original-products="original">
54
+ {% for product in products %}
55
+ <div class="item-product bg-btn-secondary text-black font-bold px-4 py-2 rounded cursor-grab transition-all"
56
+ data-id-product="{{ product.id }}" data-code="{{ product.codigoProducto }}">
57
+ {{ product.codigoProducto }}
58
+ </div>
59
+ {% endfor %}
60
+ </div>
61
+
62
+ <!-- Cartesian Plane Container -->
63
+ <div class="flex justify-center w-full">
64
+ <div class="relative w-full max-w-[800px] aspect-[3/2] bg-white border-2 border-gray-400 shadow-inner cursor-crosshair"
65
+ id="napping-plane"
66
+ style="background-image: linear-gradient(#e5e7eb 1px, transparent 1px), linear-gradient(90deg, #e5e7eb 1px, transparent 1px); background-size: 20px 20px;">
67
+
68
+ <span class="absolute bottom-1 right-2 text-xs text-gray-500 font-bold">60cm (X)</span>
69
+ <span class="absolute top-2 left-1 text-xs text-gray-500 font-bold">40cm (Y)</span>
70
+ <span class="absolute bottom-1 left-1 text-xs text-gray-500 font-bold">0</span>
71
+
72
+ {% for point in data_points %}
73
+ <div id="point-{{ point.code }}"
74
+ class="data-point absolute w-4 h-4 bg-red-600 rounded-full transform -translate-x-1/2 -translate-y-1/2 cursor-pointer border-2 border-white shadow-md group"
75
+ data-px="{{ point.px }}" data-py="{{ point.py }}" data-code="{{ point.code }}"
76
+ data-id-product="{{ point.id_product }}">
77
+ <div
78
+ class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-gray-800 text-white text-xs rounded whitespace-nowrap z-10 hidden group-hover:block">
79
+ <strong>{{ point.code }}</strong><br>
80
+ X: {{ point.px }}<br>
81
+ Y: {{ point.py }}
82
+ </div>
83
+ <span
84
+ class="absolute top-4 left-1/2 transform -translate-x-1/2 text-xs font-bold text-gray-700 pointer-events-none">
85
+ {{ point.code }}
86
+ </span>
87
+ </div>
88
+ {% endfor %}
89
+ </div>
90
+ </div>
91
+
92
+ <section role="alert" class="alert alert-info">
93
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
94
+ class="h-6 w-6 shrink-0 stroke-current">
95
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
96
+ d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 0 0118 0z"></path>
97
+ </svg>
98
+ <span class="text-lg">
99
+ Para guardar los datos sin finalizar su sesión use el botón
100
+ <b>"Guardar progreso"</b>
101
+ </span>
102
+ </section>
103
+
104
+ <section role="alert" class="alert alert-warning">
105
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none"
106
+ viewBox="0 0 24 24">
107
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
108
+ 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" />
109
+ </svg>
110
+ <div class="flex-col">
111
+ <span class="text-lg block">
112
+ Si usas el botón <b>“Salir de la sesión”</b> asegúrese de guardar el progreso de lo contrario
113
+ perderás todo lo que no hayas guardado antes
114
+ </span>
115
+ <span class="text-lg block">
116
+ Con el botón <b>“Finalizar”</b>, se guardan las posiciones, sales de la sesión y no
117
+ podrás ingresar otra vez
118
+ </span>
119
+ </div>
120
+ </section>
121
+
122
+ <section class="flex justify-end gap-4 max-sm:flex-col">
123
+ <button id="save-progress" class="cts-btn-general cts-btn-primary btn-push">
124
+ Guardar progreso
125
+ </button>
126
+ <div class="flex gap-2">
127
+ <button id="question-save" class="cts-btn-general cts-btn-secondary btn-push flex-1">
128
+ ¿Finalizar sesión?
129
+ </button>
130
+ <button id="finish-session" class="cts-btn-general cts-btn-tertiary btn-push hidden flex-1"
131
+ onclick="finishSession('form-actions')">
132
+ Finalizar
133
+ </button>
134
+ <button id="cancel-save" class="cts-btn-general cts-btn-error btn-push hidden flex-1">
135
+ Cancelar
136
+ </button>
137
+ </div>
138
+ <span id="loading-data-save" class="loading loading-spinner loading-xl text-accent hidden"></span>
139
+ </section>
140
+ </article>
141
+
142
+ {% include "../components/toast-container.html" %}
143
+ </article>
144
+ </article>
145
+ {% endblock %}
146
+
147
+ {% block extra_js %}
148
+ <script src="{% static 'js/actions-form.js' %}"></script>
149
+ <script src="{% static 'js/test-napping.js' %}"></script>
150
+ {% endblock %}
tecnicas/templates/tecnicas/manage_sesions/details-session-napping.html ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 relative max-w-4xl">
9
+ <header class="text-center flex-row max-sm:flex-col w-full flex justify-around items-center flex-wrap gap-10">
10
+ <h1 class="text-black rounded-xl font-bold text-2xl bg-surface-card shadow-lg p-4 flex-1">
11
+ Detalles de la sesión
12
+ </h1>
13
+ <a href="{% url 'cata_system:panel_sesiones' page=1 %}">
14
+ <button class="cts-btn-general cts-btn-error btn-push">
15
+ Volver a las Sesiones
16
+ </button>
17
+ </a>
18
+ </header>
19
+
20
+ {% if message %}
21
+ {% include "../components/error-message.html" with message=message %}
22
+ {% endif %}
23
+ {% if error %}
24
+ {% include "../components/error-message.html" with message=error %}
25
+ {% endif %}
26
+
27
+ <p class="text-black font-bold text-2xl border-b-2">
28
+ Información general
29
+ </p>
30
+ <article
31
+ class="text-black rounded-xl grid grid-cols-2 max-sm:grid-cols-1 gap-3 max-sm:gap-1 text-xl max-sm:text-base">
32
+ <section
33
+ class="col-span-2 max-sm:col-span-1 bg-surface-ligt flex flex-wrap items-center justify-center max-sm:justify-normal gap-x-2 p-4 rounded-2xl">
34
+ <p class="font-bold">
35
+ Código:
36
+ </p>
37
+ <p class="font-sans">
38
+ {{ session.codigo_sesion }}
39
+ </p>
40
+ </section>
41
+
42
+ <section
43
+ class="col-span-2 max-sm:col-span-1 bg-surface-card flex flex-wrap items-center justify-center max-sm:justify-normal gap-x-2 p-4 rounded-2xl">
44
+ <p class="font-bold">
45
+ Nombre:
46
+ </p>
47
+ <p class="antialiased">
48
+ {% if session.nombre_sesion %}
49
+ {{ session.nombre_sesion }}
50
+ {% else %}
51
+ Sin nombre
52
+ {% endif %}
53
+ </p>
54
+ </section>
55
+
56
+ <section
57
+ class="bg-surface-card flex flex-wrap items-center justify-center max-sm:justify-normal gap-x-2 p-4 rounded-2xl">
58
+ <p class="font-bold">
59
+ Fecha creación:
60
+ </p>
61
+ <p class="font-sans font-normal uppercase">
62
+ {{ session.fechaCreacion }}
63
+ </p>
64
+ </section>
65
+
66
+ <section
67
+ class="bg-surface-card flex flex-wrap items-center justify-center max-sm:justify-normal gap-x-2 p-4 rounded-2xl">
68
+ <p class="font-bold">
69
+ Estado:
70
+ </p>
71
+ <p class="font-sans text-lg max-sm:text-base font-normal">
72
+ {{ status }}
73
+ </p>
74
+ </section>
75
+
76
+ <section
77
+ class="bg-surface-ligt flex flex-wrap items-center justify-center max-sm:justify-normal gap-x-2 p-4 rounded-2xl">
78
+ <p class="font-bold">
79
+ Técnica:
80
+ </p>
81
+ <p class="font-sans text-lg max-sm:text-base font-normal uppercase">
82
+ {{ session.tecnica.tipo_tecnica }}
83
+ </p>
84
+ </section>
85
+
86
+ <section
87
+ class="bg-surface-card flex flex-wrap items-center justify-center max-sm:justify-normal gap-x-2 p-4 rounded-2xl">
88
+ <p class="font-sans">
89
+ <b>Catadores Max.</b> {{ session.tecnica.limite_catadores }}
90
+ </p>
91
+ </section>
92
+
93
+ {% if repetition %}
94
+ <section
95
+ class="bg-surface-card flex flex-wrap items-center justify-center max-sm:justify-normal gap-x-2 p-4 rounded-2xl">
96
+ <p class="font-sans">
97
+ <b>Repetición:</b> {{ repetition }}
98
+ </p>
99
+ </section>
100
+ {% endif %}
101
+
102
+ <section
103
+ class="col-span-2 max-sm:col-span-1 bg-surface-card flex flex-wrap items-center justify-center max-sm:justify-normal gap-x-2 p-4 rounded-2xl">
104
+ <p class="font-bold">
105
+ Instrucciones:
106
+ </p>
107
+ <p class="font-sans text-xl max-sm:text-base font-normal">
108
+ {{ session.tecnica.instrucciones }}
109
+ </p>
110
+ </section>
111
+ </article>
112
+
113
+ <p class="text-black font-bold text-2xl border-b-2">
114
+ Modalidades disponibles
115
+ </p>
116
+ <article class="grid grid-cols-2 gap-4 max-sm:gap-2">
117
+ {% if not session.activo %}
118
+ {% for mode in modes %}
119
+ <button
120
+ class="cts-btn-general capitalize cts-btn-primary btn-push flex flex-col justify-center items-center gap-2"
121
+ data-mode="{{ mode.nombre }}" onclick="startSession('{{ mode.nombre }}')">
122
+ Napping {% if mode.nombre != "sin modalidad" %} {{ mode.nombre }} {% endif %}
123
+ <figure class="w-10">
124
+ <img src="{% static 'img/giro.svg' %}" alt="flechas girando" class="invert">
125
+ </figure>
126
+ </button>
127
+ {% endfor %}
128
+ {% else %}
129
+ <a href="{% url 'cata_system:monitor_sesion' session_code=session.codigo_sesion %}" class="w-full">
130
+ <button
131
+ class="cts-btn-general capitalize active:border-orange-500 border-orange-800 bg-orange-600 btn-push flex flex-col justify-center items-center gap-2 w-full">
132
+ Monitorear repetición
133
+ <figure class="w-10">
134
+ <img src="{% static 'img/monitor.svg' %}" alt="flechas girando" class="invert">
135
+ </figure>
136
+ </button>
137
+ </a>
138
+ {% endif %}
139
+ <button
140
+ class="uppercase text-lg cts-btn-general cts-btn-error btn-push flex flex-col justify-center items-center gap-2 w-full"
141
+ onclick="showWarningDialog('cts-warnig-dialog')">
142
+ borrar
143
+ <figure class="w-10">
144
+ <img src="{% static 'img/basura.svg' %}" alt="bote de basura" class="invert">
145
+ </figure>
146
+ </button>
147
+ </article>
148
+
149
+ <section
150
+ class="absolute w-full h-full flex justify-center items-center bg-surface-alter-card/70 top-0 left-0 p-6 rounded-xl cts-warnig-dialog hidden">
151
+ <article class="bg-surface-card p-4 space-y-6 rounded-xl">
152
+ <h2 class="text-4xl text-black text-center">
153
+ ¡¡¡Cuidado!!!
154
+ </h2>
155
+ <p class="text-2xl text-black text-center">
156
+ Esta acción no es reversible, si la continua también borrara todos los datos recopilados
157
+ relacionados a esta sesión y no podrá volver a recuperarlos
158
+ </p>
159
+ <section class="text-black font-bold text-xl space-y-4">
160
+ <p class="text-center">
161
+ ¿Estás seguro de continuar?
162
+ </p>
163
+ <div class="flex max-sm:flex-col gap-4 justify-center">
164
+ <button class="cts-btn-general cts-btn-error btn-push" onclick="deleteSession()">
165
+ Estoy seguro
166
+ </button>
167
+ <button class="cts-btn-general cts-btn-primary btn-push"
168
+ onclick="hiddenWarningDialog('cts-warnig-dialog')">
169
+ No, cancelar
170
+ </button>
171
+ </div>
172
+ </section>
173
+ </article>
174
+ </section>
175
+
176
+ <p class="text-black font-bold text-2xl border-b-2">
177
+ Datos obtenidos
178
+ </p>
179
+
180
+ {% if there_data %}
181
+ {% include "../components/table-napping-no-mode.html" with testers=testers coordinates_no_mode=coordinates_no_mode %}
182
+ {% else %}
183
+ {% include "../components/error-message.html" with message='Sin datos por mostrar aún' %}
184
+ {% endif %}
185
+
186
+
187
+ <form action="" method="post" class="form-action-session hidden">
188
+ <input type="hidden" name="action" class="action-option">
189
+ {% csrf_token %}
190
+ </form>
191
+ </article>
192
+ </article>
193
+ {% endblock %}
194
+
195
+ {% block extra_js %}
196
+ <script src="{% static 'js/details-session.js' %}"></script>
197
+ <script src="{% static 'js/showHiddenElement.js' %}"></script>
198
+ {% endblock %}
tecnicas/urls.py CHANGED
@@ -122,6 +122,10 @@ urlpatterns = [
122
  views.sortTest,
123
  name="session_sort"),
124
 
 
 
 
 
125
 
126
  # APIs
127
  path("presenter/api/nueva-etiqueta",
@@ -151,4 +155,8 @@ urlpatterns = [
151
  path("testers/api/rating-sort",
152
  views.ratingSort,
153
  name="api_rating_sort"),
 
 
 
 
154
  ]
 
122
  views.sortTest,
123
  name="session_sort"),
124
 
125
+ path("testers/init-session/<str:code_sesion>/nappping",
126
+ views.nappingTest,
127
+ name="session_napping"),
128
+
129
 
130
  # APIs
131
  path("presenter/api/nueva-etiqueta",
 
155
  path("testers/api/rating-sort",
156
  views.ratingSort,
157
  name="api_rating_sort"),
158
+
159
+ path("testers/api/rating-napping/no-mode",
160
+ views.ratingNappingNoMode,
161
+ name="api_rating_napping_no_mode"),
162
  ]
tecnicas/views/__init__.py CHANGED
@@ -29,6 +29,7 @@ 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
  from .apis.rating_sort import ratingSort
 
32
 
33
  from .tester_forms.init_tester_form import initTesterForm
34
  from .tester_forms.panel_main_tester import mainPanelTester
@@ -38,3 +39,4 @@ from .tester_forms.convencional_scales import convencionalScales
38
  from .tester_forms.cata_test import cataTest
39
  from .tester_forms.pf_test import pfTest
40
  from .tester_forms.sort_test import sortTest
 
 
29
  from .apis.rating_word_scales import ratingWordScales
30
  from .apis.rating_word_cata import ratingWordCata
31
  from .apis.rating_sort import ratingSort
32
+ from .apis.rating_napping import ratingNappingNoMode
33
 
34
  from .tester_forms.init_tester_form import initTesterForm
35
  from .tester_forms.panel_main_tester import mainPanelTester
 
39
  from .tester_forms.cata_test import cataTest
40
  from .tester_forms.pf_test import pfTest
41
  from .tester_forms.sort_test import sortTest
42
+ from .tester_forms.napping_test import nappingTest
tecnicas/views/apis/rating_napping.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.http import HttpRequest, JsonResponse
2
+ from tecnicas.controllers import RatingNappingController
3
+ import json
4
+
5
+
6
+ def ratingNappingNoMode(req: HttpRequest):
7
+ if req.method == "POST":
8
+ try:
9
+ data = json.loads(req.body.decode("utf-8"))
10
+ response = RatingNappingController.saveRatingCoordinates(
11
+ request=req, data=data)
12
+ return response
13
+ except Exception as e:
14
+ return JsonResponse({"error": "Error al procesar datos"})
15
+ else:
16
+ return JsonResponse({"error": "Método no permitido"})
tecnicas/views/sessions_management/session_details.py CHANGED
@@ -3,7 +3,7 @@ from django.shortcuts import redirect
3
  from django.urls import reverse
4
  from tecnicas.models import SesionSensorial
5
  from tecnicas.utils import noValidTechnique
6
- from tecnicas.controllers import DetallesController, DetallesEscalasController, DetallesCATAController, DetallesPFController, DetallesSortController
7
 
8
 
9
  def sessionDetails(req: HttpRequest, session_code: str):
@@ -43,6 +43,12 @@ def sessionDetails(req: HttpRequest, session_code: str):
43
  response = controller_view.controllGetResponse(
44
  request=req, message=message)
45
 
 
 
 
 
 
 
46
  else:
47
  response = noValidTechnique(
48
  params={"page": 1},
@@ -51,6 +57,7 @@ def sessionDetails(req: HttpRequest, session_code: str):
51
  },
52
  name_view="cata_system:panel_sesiones"
53
  )
 
54
  return response
55
 
56
  elif req.method == "POST":
@@ -58,19 +65,27 @@ def sessionDetails(req: HttpRequest, session_code: str):
58
  codigo_sesion=session_code)
59
 
60
  use_techinique = sensorial_session.tecnica.tipo_tecnica.nombre_tecnica
61
-
62
  controller_view = DetallesController(sensorial_session)
63
 
 
 
64
  if use_techinique in ["escalas", "rata", "cata", "perfil flash", "sort"]:
65
- if req.POST.get("action") == "start_session":
66
  response = controller_view.startRepetition(
67
  presenter=req.user.user_presentador, request=req)
68
 
69
- elif req.POST.get("action") == "delete_session":
70
  controller_view.deleteSesorialSession()
71
  response = redirect(
72
  reverse("cata_system:panel_sesiones", kwargs={"page": 1}))
73
 
 
 
 
 
 
 
 
74
  else:
75
  response = controller_view.controllGetResponse(
76
  error="No se reconoce la acción a realizar")
 
3
  from django.urls import reverse
4
  from tecnicas.models import SesionSensorial
5
  from tecnicas.utils import noValidTechnique
6
+ from tecnicas.controllers import DetallesController, DetallesEscalasController, DetallesCATAController, DetallesPFController, DetallesSortController, DetallesNappingController
7
 
8
 
9
  def sessionDetails(req: HttpRequest, session_code: str):
 
43
  response = controller_view.controllGetResponse(
44
  request=req, message=message)
45
 
46
+ elif use_techinique == "napping":
47
+ controller_view = DetallesNappingController(
48
+ session=sensorial_session)
49
+ response = controller_view.controllGetResponse(
50
+ request=req, message=message)
51
+
52
  else:
53
  response = noValidTechnique(
54
  params={"page": 1},
 
57
  },
58
  name_view="cata_system:panel_sesiones"
59
  )
60
+
61
  return response
62
 
63
  elif req.method == "POST":
 
65
  codigo_sesion=session_code)
66
 
67
  use_techinique = sensorial_session.tecnica.tipo_tecnica.nombre_tecnica
 
68
  controller_view = DetallesController(sensorial_session)
69
 
70
+ action = req.POST.get("action")
71
+
72
  if use_techinique in ["escalas", "rata", "cata", "perfil flash", "sort"]:
73
+ if action == "start_session":
74
  response = controller_view.startRepetition(
75
  presenter=req.user.user_presentador, request=req)
76
 
77
+ elif action == "delete_session":
78
  controller_view.deleteSesorialSession()
79
  response = redirect(
80
  reverse("cata_system:panel_sesiones", kwargs={"page": 1}))
81
 
82
+ elif use_techinique == "napping":
83
+ controller_view = DetallesNappingController(
84
+ session=sensorial_session)
85
+
86
+ response = controller_view.controllPostResponse(
87
+ request=req, action=action)
88
+
89
  else:
90
  response = controller_view.controllGetResponse(
91
  error="No se reconoce la acción a realizar")
tecnicas/views/sessions_management/session_monitor.py CHANGED
@@ -7,7 +7,7 @@ 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, MonitorSortController
11
  from tecnicas.utils import noValidTechnique
12
 
13
 
@@ -29,6 +29,10 @@ def sessionMonitor(req: HttpRequest, session_code: str):
29
  controll_view = MonitorSortController(sensorial_session)
30
  response = controll_view.controllGetResponse(request=req)
31
 
 
 
 
 
32
  else:
33
  response = noValidTechnique(
34
  params={
@@ -89,6 +93,17 @@ def sessionMonitor(req: HttpRequest, session_code: str):
89
  response = controll_view.controlGetResponse(
90
  request=req, error="No se ha definido la acción a realizar")
91
 
 
 
 
 
 
 
 
 
 
 
 
92
  else:
93
  response = noValidTechnique(
94
  params={
 
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, MonitorSortController, MonitorNappingController
11
  from tecnicas.utils import noValidTechnique
12
 
13
 
 
29
  controll_view = MonitorSortController(sensorial_session)
30
  response = controll_view.controllGetResponse(request=req)
31
 
32
+ elif use_techinique == "napping":
33
+ controll_view = MonitorNappingController(sensorial_session)
34
+ response = controll_view.controllGetResponse(request=req)
35
+
36
  else:
37
  response = noValidTechnique(
38
  params={
 
93
  response = controll_view.controlGetResponse(
94
  request=req, error="No se ha definido la acción a realizar")
95
 
96
+ elif use_techinique == "napping":
97
+ controll_view = MonitorNappingController(sensorial_session)
98
+ action = req.POST["action"]
99
+
100
+ if action == "finish_session":
101
+ response = controll_view.controllPostFinishSession(
102
+ request=req)
103
+ else:
104
+ response = controll_view.controlGetResponse(
105
+ request=req, error="No se ha definido la acción a realizar")
106
+
107
  else:
108
  response = noValidTechnique(
109
  params={
tecnicas/views/tester_forms/init_tester_form.py CHANGED
@@ -1,6 +1,6 @@
1
  from django.http import HttpRequest, JsonResponse
2
  from django.shortcuts import render
3
- from tecnicas.controllers import InitSessionEscalasController, InitSessionRATAController, InitSessionPFController, InitSessionSortController
4
  from tecnicas.models import SesionSensorial
5
 
6
 
@@ -30,10 +30,15 @@ def initTesterForm(req: HttpRequest, code_sesion: str):
30
  sensorial_session=session, user_tester=req.user.user_catador)
31
  response = view_controller.controllGet(request=req)
32
 
 
 
 
 
 
33
  else:
34
  context = {
35
  "session": session,
36
- "error": "La técnica usada en esta sesión o ha sido implementada para ingresar a ella"
37
  }
38
  response = render(
39
  req, template_url, context)
@@ -41,7 +46,7 @@ def initTesterForm(req: HttpRequest, code_sesion: str):
41
  return response
42
 
43
  elif req.method == "POST":
44
- if type_technique == "escalas" or type_technique == "rata" or type_technique == "cata":
45
  view_controller = InitSessionEscalasController(
46
  sensorial_session=session, user_tester=req.user.user_catador)
47
  response = view_controller.controllPost(request=req)
@@ -55,7 +60,12 @@ def initTesterForm(req: HttpRequest, code_sesion: str):
55
  view_controller = InitSessionSortController(
56
  sensorial_session=session, user_tester=req.user.user_catador)
57
  response = view_controller.controllPost(request=req)
58
-
 
 
 
 
 
59
  else:
60
  context = {
61
  "session": session,
 
1
  from django.http import HttpRequest, JsonResponse
2
  from django.shortcuts import render
3
+ from tecnicas.controllers import InitSessionEscalasController, InitSessionRATAController, InitSessionPFController, InitSessionSortController, InitSessionNappingController
4
  from tecnicas.models import SesionSensorial
5
 
6
 
 
30
  sensorial_session=session, user_tester=req.user.user_catador)
31
  response = view_controller.controllGet(request=req)
32
 
33
+ elif type_technique == "napping":
34
+ view_controller = InitSessionNappingController(
35
+ sensorial_session=session, user_tester=req.user.user_catador)
36
+ response = view_controller.controllGet(request=req)
37
+
38
  else:
39
  context = {
40
  "session": session,
41
+ "error": "La técnica usada en esta sesión no ha sido implementada para ingresar a ella"
42
  }
43
  response = render(
44
  req, template_url, context)
 
46
  return response
47
 
48
  elif req.method == "POST":
49
+ if type_technique in ["escalas", "rata", "cata"]:
50
  view_controller = InitSessionEscalasController(
51
  sensorial_session=session, user_tester=req.user.user_catador)
52
  response = view_controller.controllPost(request=req)
 
60
  view_controller = InitSessionSortController(
61
  sensorial_session=session, user_tester=req.user.user_catador)
62
  response = view_controller.controllPost(request=req)
63
+
64
+ elif type_technique == "napping":
65
+ view_controller = InitSessionNappingController(
66
+ sensorial_session=session, user_tester=req.user.user_catador)
67
+ response = view_controller.controllPost(request=req)
68
+
69
  else:
70
  context = {
71
  "session": session,
tecnicas/views/tester_forms/login_session.py CHANGED
@@ -27,17 +27,14 @@ def loginSessionTester(req: HttpRequest):
27
 
28
  type_technique = session.tecnica.tipo_tecnica.nombre_tecnica
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
-
39
- elif type_technique == "sort":
40
- response = login_controller.validateEntrySort(request=req)
41
 
42
  else:
43
  context = {
 
27
 
28
  type_technique = session.tecnica.tipo_tecnica.nombre_tecnica
29
 
30
+ if type_technique in ["escalas", "perfil flash", "sort"]:
31
+ response = login_controller.validateEntryLimitTesters(request=req)
32
 
33
+ elif type_technique in ["rata", "cata"]:
34
+ response = login_controller.validateEntryRataCata(request=req)
35
 
36
+ elif type_technique == "napping":
37
+ response = login_controller.validateEntryNapping(request=req)
 
 
 
38
 
39
  else:
40
  context = {
tecnicas/views/tester_forms/napping_test.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.http import HttpRequest
2
+ from tecnicas.models import SesionSensorial
3
+ from tecnicas.controllers import TestNappingController
4
+ from tecnicas.utils import noValidTechnique
5
+
6
+
7
+ def nappingTest(req: HttpRequest, code_sesion: str):
8
+ if req.method == "GET":
9
+ session = SesionSensorial.objects.get(codigo_sesion=code_sesion)
10
+ controll_view = TestNappingController(
11
+ sensorial_session=session, user_tester=req.user.user_catador)
12
+ return controll_view.controllGet(request=req)
13
+
14
+ elif req.method == "POST":
15
+ # Se usa para finalizar la sesión
16
+ session = SesionSensorial.objects.get(codigo_sesion=code_sesion)
17
+ controll_view = TestNappingController(
18
+ sensorial_session=session, user_tester=req.user.user_catador)
19
+ return controll_view.controllPost(request=req)
20
+
21
+ else:
22
+ return noValidTechnique(
23
+ name_view="cata_system:catador_init_session",
24
+ query_params={
25
+ "error": "Método no valido"
26
+ }
27
+ )