diff --git a/tecnicas/admin.py b/tecnicas/admin.py
index 44cd4bff89696421eb2f4bcfc134e5b20e34e65c..b5a2134d0ca3a93f805656ba89565671188908ea 100644
--- a/tecnicas/admin.py
+++ b/tecnicas/admin.py
@@ -1,20 +1,6 @@
from django.contrib import admin
-from .models import CategoriaTecnica, TipoTecnica, TipoEscala, EstiloPalabra
-
-from .models import Catador, Presentador
-
-from .models import Tecnica, SesionSensorial
-
-from .models import EsAtributo, Palabra, Vocabulario
-
-from .models import Etiqueta, Escala, EtiquetasEscala
-
-from .models import Producto, Participacion
-
-from .models import Orden, Posicion
-
-from .models import Dato, ValorDecimal, ValorBooleano, Calificacion
+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
# Register your models here.
admin.site.register(CategoriaTecnica)
@@ -46,3 +32,11 @@ admin.site.register(Dato)
admin.site.register(ValorDecimal)
admin.site.register(ValorBooleano)
admin.site.register(Calificacion)
+
+admin.site.register(ListaPalabras)
+admin.site.register(GrupoProducto)
+
+admin.site.register(Modalidad)
+admin.site.register(TecnicaModalidad)
+
+admin.site.register(DatoPunto)
diff --git a/tecnicas/controllers/__init__.py b/tecnicas/controllers/__init__.py
index 8e61f1aca6b63b7d8b535b90382d9bbd48311d34..b6c2ff8c68c4ee7449db34f6d1806ccafd786801 100644
--- a/tecnicas/controllers/__init__.py
+++ b/tecnicas/controllers/__init__.py
@@ -16,22 +16,53 @@ from .views_controller.create_session.panel_basic_controller import PanelBasicCo
from .views_controller.create_session.panel_tags_controller import PanelTagsController
from .views_controller.create_session.panel_codes_controller import PanelCodesController
from .views_controller.create_session.panel_words_controller import PanelWordsController
-from .views_controller.create_session.panel_create_controller import PanelCreateController
-from .views_controller.session_management.details_controller import DetallesController
-from .views_controller.session_management.details_escala_controller import DetallesEscalasController
-from .views_controller.session_management.details_rata_controller import DetallesRATAController
-from .views_controller.session_management.monitor_controller import MonitorController
-from .views_controller.session_management.monitor_escalas_controller import MonitorEscalasController
-from .views_controller.session_management.monitor_rata_controller import MonitorRATAController
+from .views_controller.create_session.panels_create.panel_create_escalas_controller import PanelCreateEscalasController
+from .views_controller.create_session.panels_create.panel_create_rata_controller import PanelCreateRataController
+from .views_controller.create_session.panels_create.panel_create_cata_controller import PanelCreateCataController
+from .views_controller.create_session.panels_create.panel_create_pf_controller import PanelCreatePFController
+from .views_controller.create_session.panels_create.panel_create_sort_controller import PanelCreateSortController
+from .views_controller.create_session.panels_create.panel_create_napping_controller import PanelCreateNappingController
+
+
+from .views_controller.session_management.details.details_controller import DetallesController
+from .views_controller.session_management.details.details_escala_controller import DetallesEscalasController
+from .views_controller.session_management.details.details_rata_controller import DetallesRATAController
+from .views_controller.session_management.details.details_cata_controller import DetallesCATAController
+from .views_controller.session_management.details.details_pf_controller import DetallesPFController
+from .views_controller.session_management.details.details_sort_controller import DetallesSortController
+from .views_controller.session_management.details.details_napping_controller import DetallesNappingController
+
+from .views_controller.session_management.monitor.monitor_escalas_controller import MonitorEscalasController
+from .views_controller.session_management.monitor.monitor_rata_controller import MonitorRATAController
+from .views_controller.session_management.monitor.monitor_pf_controller import MonitorPFController
+from .views_controller.session_management.monitor.monitor_sort_controller import MonitorSortController
+from .views_controller.session_management.monitor.monitor_napping_controller import MonitorNappingController
from .views_controller.sessions_tester.login_session_tester_controller import LoginSessionTesterController
-from .views_controller.sessions_tester.init_session_tester_controller import InitSessionTesterController
-from .views_controller.sessions_tester.convencional_scales_controller import ConvencionalScalesController
from .views_controller.sessions_tester.list_sessions_tester_controller import ListSessionsTesterController
+from .views_controller.sessions_tester.tests_forms.test_scales_controller import TestScalesController
+from .views_controller.sessions_tester.tests_forms.test_rata_controller import TestRataController
+from .views_controller.sessions_tester.tests_forms.test_cata_controller import TestCataController
+from .views_controller.sessions_tester.tests_forms.test_pf_controller import TestPFController
+from .views_controller.sessions_tester.tests_forms.test_sort_controller import TestSortController
+from .views_controller.sessions_tester.tests_forms.test_napping_controller import TestNappingController
+
+from .views_controller.sessions_tester.init_session.init_session_escalas_controller import InitSessionEscalasController
+from .views_controller.sessions_tester.init_session.init_session_rata_controller import InitSessionRATAController
+from .views_controller.sessions_tester.init_session.init_session_pf_controller import InitSessionPFController
+from .views_controller.sessions_tester.init_session.init_session_sort_controller import InitSessionSortController
+from .views_controller.sessions_tester.init_session.init_session_napping_controller import InitSessionNappingController
+
from .views_controller.vocabulary_manage.create_vocabulary_controller import CreateVocabularyController
+from .views_controller.vocabulary_manage.view_vocabulary_controller import ViewVocabularyController
from .views_controller.vocabulary_manage.list_vocabulary_controller import ListVocabularyController
-from .views_controller.api_rating_controller import ApiRatingController
+from .api_controller.rating_sacales_controller import RatingScalesController
+from .api_controller.rating_cata_controller import RatingCataController
+from .api_controller.rating_pf_list_controller import RatingPFListController
from .views_controller.tester_list_controller import TesterListController
+from .api_controller.rating_sort_controller import RatingSortController
+from .api_controller.rating_napping_controller import RatingNappingController
+
diff --git a/tecnicas/controllers/api_controller/rating_cata_controller.py b/tecnicas/controllers/api_controller/rating_cata_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..f263c0a7f4114d7c4b7cf82effe80214a5f5cb67
--- /dev/null
+++ b/tecnicas/controllers/api_controller/rating_cata_controller.py
@@ -0,0 +1,78 @@
+from django.http import JsonResponse, HttpRequest
+from django.db import transaction
+from tecnicas.models import Calificacion, Dato, ValorBooleano, Participacion, Palabra
+
+
+class RatingCataController():
+ def __init__(self):
+ pass
+
+ @staticmethod
+ def saveRatingWords(request: HttpRequest, data_words: list[dict], data_prodct: dict):
+ try:
+ with transaction.atomic():
+ participation = Participacion.objects.get(
+ id=request.session["id_participation"])
+ if not participation:
+ raise ValueError("No está autorizado en la sesión")
+
+ ids_words = []
+ words_values = {}
+
+ # Acoplar datos para usar
+ for da_wo in data_words:
+ ids_words.append(da_wo["id"])
+ words_values[da_wo["word"]] = da_wo["is_check"]
+
+ words_for_rating = Palabra.objects.filter(id__in=ids_words)
+ if not words_for_rating:
+ raise ValueError("No se han encontrado sus palabras")
+
+ technique = participation.tecnica
+
+ # Creando la calificacion
+ (rating, creataed) = Calificacion.objects.get_or_create(
+ num_repeticion=technique.repeticion,
+ id_producto_id=data_prodct["id"],
+ id_tecnica=technique,
+ id_catador=request.user.user_catador
+ )
+ if not rating:
+ raise ValueError("Problemas al crear la calificación")
+
+ # Guardando datos
+ data_for_save = []
+ for word in words_for_rating:
+ data_for_save.append(Dato(
+ id_palabra=word,
+ id_calificacion=rating
+ ))
+
+ Dato.objects.bulk_create(data_for_save)
+ data_saved = Dato.objects.filter(
+ id_calificacion=rating).only("id_palabra")
+ if not data_saved:
+ raise ValueError("Problemas al crear los datos")
+
+ # Guardando valores de datos
+ values_for_save = []
+ for data in data_saved:
+ word_for_rating = data.id_palabra.nombre_palabra
+ values_for_save.append(
+ ValorBooleano(
+ id_dato=data,
+ valor=words_values[word_for_rating]
+ )
+ )
+
+ ValorBooleano.objects.bulk_create(values_for_save)
+ values_saves = ValorBooleano.objects.filter(
+ id_dato__id_calificacion=rating).count()
+ if not values_saves:
+ raise ValueError("Error al guardar los datos")
+
+ return JsonResponse({"message": "Valores guardados"})
+
+ except ValueError as e:
+ print(f"Error de calificacion: {e}")
+ return JsonResponse({"error": e}, statusstatus=500)
diff --git a/tecnicas/controllers/api_controller/rating_napping_controller.py b/tecnicas/controllers/api_controller/rating_napping_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6bcaf47aa2f766116c74d328dbd09ac06fa01aa
--- /dev/null
+++ b/tecnicas/controllers/api_controller/rating_napping_controller.py
@@ -0,0 +1,367 @@
+from django.http import JsonResponse
+from django.http import HttpRequest
+from django.db import transaction
+from tecnicas.models import Calificacion, DatoPunto, Producto, Participacion, Palabra, GrupoProducto, TecnicaModalidad
+from tecnicas.forms import ListWordsForm
+
+
+class RatingNappingController:
+ @staticmethod
+ def saveRatingCoordinates(request: HttpRequest, data: list | dict):
+ participation = Participacion.objects.get(
+ id=request.session["id_participation"]
+ )
+
+ name_mod = TecnicaModalidad.objects.get(
+ tecnica=participation.tecnica
+ ).modalidad.nombre.lower()
+
+ # Branch based on modality
+ if name_mod == 'sorting':
+ return RatingNappingController.processSortingMode(
+ data, participation
+ )
+
+ if name_mod in ['sin modalidad', 'perfil ultra flash']:
+ return RatingNappingController.processNappOrPUF(
+ data, participation
+ )
+
+ else:
+ return JsonResponse({"error": "Modalidad no soportada"})
+
+ @staticmethod
+ def processSortingMode(data: dict, participation):
+ try:
+ with transaction.atomic():
+ # Extract products array (always present)
+ products = data.get("products", [])
+ if not products:
+ return JsonResponse({"error": "No se proporcionaron productos"})
+
+ existing_ratings_map, products_map = RatingNappingController.savePoints(
+ isSorting=True, products=products, participation=participation
+ )
+
+ # Process groups if they exist
+ groups = data.get("groups", {})
+ if groups:
+ RatingNappingController.processGroupsForSorting(
+ products, groups, participation, existing_ratings_map, products_map
+ )
+ else:
+ RatingNappingController.deleteAllGroups(participation)
+
+ return JsonResponse({"message": "Datos guardados exitosamente"})
+
+ except Exception as e:
+ print("ERROR:", e)
+ import traceback
+ traceback.print_exc()
+ return JsonResponse({"error": f"Error al procesar datos: {str(e)}"})
+
+ @staticmethod
+ def processNappOrPUF(data: list, participation):
+ try:
+ with transaction.atomic():
+ existing_ratings_map, products_map = RatingNappingController.savePoints(
+ isSorting=False, products=data, participation=participation
+ )
+
+ RatingNappingController.processWordsForRatings(
+ data, existing_ratings_map
+ )
+
+ return JsonResponse({"message": "Datos guardados exitosamente"})
+
+ except Exception as e:
+ print("ERROR:", e)
+ return JsonResponse({"error": "Error al procesar datos"})
+
+ @staticmethod
+ def processGroupsForSorting(products, groups, participation, existing_ratings_map, products_map):
+ """Process groups for sorting mode
+ - Creates/updates GrupoProducto instances
+ - Ensures products don't belong to multiple groups
+ - Associates words with groups
+ """
+ # Build mapping of product_id to group_id from products array
+ product_to_group = {}
+ for product_item in products:
+ group_code = product_item.get("group", "")
+ if group_code: # Only if product has a group assigned
+ product_id = int(product_item["idProduct"])
+ if product_id in product_to_group:
+ raise ValueError(
+ f"Producto {product_id} pertenece a múltiples grupos")
+ product_to_group[product_id] = group_code
+
+ # Get existing groups for this catador and technique
+ existing_groups = GrupoProducto.objects.filter(
+ tecnica=participation.tecnica,
+ catador=participation.catador
+ )
+
+ # Create a map of existing groups by their product composition
+ # We'll identify groups by the set of products they contain
+ existing_groups_map = {}
+ for group in existing_groups:
+ product_ids = set(group.productos.values_list('id', flat=True))
+ key = frozenset(product_ids)
+ existing_groups_map[key] = group
+
+ # Build new groups structure
+ groups_to_create = []
+ groups_to_update = []
+ group_products_map = {} # group_id -> [product_ids]
+
+ # Organize products by group
+ for product_id, group_code in product_to_group.items():
+ if group_code not in group_products_map:
+ group_products_map[group_code] = []
+ group_products_map[group_code].append(product_id)
+
+ # Process each group
+ for group_code, product_ids in group_products_map.items():
+ product_set = frozenset(product_ids)
+
+ # Check if this group already exists
+ if product_set in existing_groups_map:
+ group = existing_groups_map[product_set]
+ groups_to_update.append((group, group_code))
+ else:
+ # Create new group
+ group = GrupoProducto(
+ tecnica=participation.tecnica,
+ catador=participation.catador
+ )
+ groups_to_create.append((group, product_ids, group_code))
+
+ # Create new groups
+ created_groups = []
+ for group, product_ids, group_code in groups_to_create:
+ group.save() # Save first to get ID for M2M
+
+ # Add products to group
+ productos = [products_map[pid]
+ for pid in product_ids if pid in products_map]
+ group.productos.set(productos)
+
+ created_groups.append((group, group_code))
+
+ # Combine created and existing groups for word processing
+ all_groups_for_words = created_groups + groups_to_update
+
+ # Delete groups that no longer exist
+ current_group_sets = set(frozenset(pids)
+ for pids in group_products_map.values())
+ for product_set, group in existing_groups_map.items():
+ if product_set not in current_group_sets:
+ group.delete()
+
+ # Process words for groups
+ if groups:
+ RatingNappingController.processWordsForGroups(
+ groups, all_groups_for_words
+ )
+
+ @staticmethod
+ def deleteAllGroups(participation):
+ GrupoProducto.objects.filter(
+ tecnica=participation.tecnica,
+ catador=participation.catador
+ ).delete()
+
+ @staticmethod
+ def processWordsForGroups(groups_data, groups_list):
+ """Process and associate words to groups
+ - Creates words that don't exist
+ - Associates words to GrupoProducto instances
+ - Handles concurrency
+ """
+ # Collect all unique words from all groups
+ all_words = set()
+ for group_id, words in groups_data.items():
+ if words:
+ all_words.update(words)
+
+ if not all_words:
+ # No words to process, just clear existing words from groups
+ for grupo, _ in groups_list:
+ grupo.palabras.clear()
+ return
+
+ # Get existing words
+ existing_words = Palabra.objects.filter(
+ nombre_palabra__in=all_words
+ )
+ existing_words_map = {w.nombre_palabra: w for w in existing_words}
+
+ # Create missing words with concurrency handling
+ word_objects = {}
+ for word_name in all_words:
+ if word_name in existing_words_map:
+ word_objects[word_name] = existing_words_map[word_name]
+ else:
+ word_obj, created = Palabra.objects.get_or_create(
+ nombre_palabra=word_name
+ )
+ word_objects[word_name] = word_obj
+
+ # Associate words with groups
+ for grupo, group_id in groups_list:
+ words = groups_data.get(group_id, [])
+ if words:
+ words_to_set = [word_objects[word_name]
+ for word_name in words if word_name in word_objects]
+ grupo.palabras.set(words_to_set)
+ else:
+ grupo.palabras.clear()
+
+ @staticmethod
+ def savePoints(isSorting: bool, products, participation):
+ try:
+ with transaction.atomic():
+ # Get products map for validation
+ products_map = RatingNappingController.getProductsMap(
+ participation.tecnica)
+
+ # Get existing ratings map
+ existing_ratings_map = RatingNappingController.getExistingRatingsMap(
+ participation.tecnica, participation.catador)
+
+ if not isSorting:
+ validation_result = RatingNappingController.validateWords(
+ products)
+ if validation_result is not None:
+ return validation_result
+
+ # Create new ratings for products that don't have them
+ new_ratings = []
+ ids_products = products_map.keys()
+ for item in products:
+ product_id = int(item["idProduct"])
+ if product_id not in existing_ratings_map and product_id in ids_products:
+ new_ratings.append(
+ Calificacion(
+ num_repeticion=0,
+ id_producto=products_map[product_id],
+ id_tecnica=participation.tecnica,
+ id_catador=participation.catador,
+ )
+ )
+
+ if new_ratings:
+ Calificacion.objects.bulk_create(new_ratings)
+ existing_ratings_map = RatingNappingController.getExistingRatingsMap(
+ participation.tecnica, participation.catador)
+
+ # Process DatoPunto instances from products array
+ existing_points_map = RatingNappingController.getExistingPointsMap(
+ existing_ratings_map.values())
+
+ points_to_create = []
+ points_to_update = []
+
+ for item in products:
+ product_id = int(item["idProduct"])
+ rating = existing_ratings_map.get(product_id)
+
+ if rating:
+ if rating.id in existing_points_map:
+ point = existing_points_map[rating.id]
+ point.x = item["x"]
+ point.y = item["y"]
+ points_to_update.append(point)
+ else:
+ points_to_create.append(
+ DatoPunto(
+ x=item["x"],
+ y=item["y"],
+ calificacion=rating,
+ )
+ )
+
+ if points_to_create:
+ DatoPunto.objects.bulk_create(points_to_create)
+
+ if points_to_update:
+ DatoPunto.objects.bulk_update(points_to_update, ['x', 'y'])
+
+ return (existing_ratings_map, products_map)
+
+ except Exception as e:
+ print(e)
+ return JsonResponse({"error": "Error al guardar los puntos"})
+
+ @staticmethod
+ def validateWords(data: list):
+ for item in data:
+ words = item.get("words", [])
+ if words:
+ dic_words = {}
+ for index, word in enumerate(words, start=1):
+ dic_words[f"palabra_{index}"] = word
+
+ form = ListWordsForm(dic_words, new_words=words)
+ if not form.is_valid():
+ errors = []
+ for field, error_list in form.errors.items():
+ errors.extend(error_list)
+ return JsonResponse({"error": f"Error en validación de palabras: {', '.join(errors)}"})
+ return None
+
+ @staticmethod
+ def processWordsForRatings(data: list, existing_ratings_map: dict):
+ all_words = set()
+ for item in data:
+ words = item.get("words", [])
+ if words:
+ all_words.update(words)
+
+ if not all_words:
+ return
+
+ existing_words = Palabra.objects.filter(
+ nombre_palabra__in=all_words
+ )
+ existing_words_map = {w.nombre_palabra: w for w in existing_words}
+
+ word_objects = {}
+ for word_name in all_words:
+ if word_name in existing_words_map:
+ word_objects[word_name] = existing_words_map[word_name]
+ else:
+ word_obj, created = Palabra.objects.get_or_create(
+ nombre_palabra=word_name
+ )
+ word_objects[word_name] = word_obj
+
+ for item in data:
+ words = item.get("words", [])
+ if words:
+ product_id = int(item["idProduct"])
+ rating = existing_ratings_map.get(product_id)
+
+ if rating:
+ words_to_set = [word_objects[word_name]
+ for word_name in words]
+ rating.palabras.set(words_to_set)
+
+ @staticmethod
+ def getProductsMap(id_tecnica):
+ products_qs = Producto.objects.filter(id_tecnica=id_tecnica)
+ return {p.id: p for p in products_qs}
+
+ @staticmethod
+ def getExistingRatingsMap(id_tecnica, id_catador):
+ ratings = Calificacion.objects.filter(
+ id_tecnica=id_tecnica,
+ id_catador=id_catador,
+ )
+ return {r.id_producto.id: r for r in ratings}
+
+ @staticmethod
+ def getExistingPointsMap(ratings):
+ points = DatoPunto.objects.filter(calificacion__in=ratings)
+ return {p.calificacion.id: p for p in points}
diff --git a/tecnicas/controllers/api_controller/rating_pf_list_controller.py b/tecnicas/controllers/api_controller/rating_pf_list_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..80e6686a0fe32850fe8fb33f6b35008722b384de
--- /dev/null
+++ b/tecnicas/controllers/api_controller/rating_pf_list_controller.py
@@ -0,0 +1,221 @@
+from django.http import JsonResponse, HttpRequest
+from django.db import transaction, IntegrityError
+from tecnicas.models import Palabra, ListaPalabras, Participacion, Calificacion, Producto, Dato, ValorDecimal
+from tecnicas.forms import ListWordsForm
+
+
+class RatingPFListController():
+ def __init__(self):
+ pass
+
+ @staticmethod
+ def getListWords(request: HttpRequest):
+ participation_current_tester = Participacion.objects.get(
+ id=request.session["id_participation"])
+
+ technique = participation_current_tester.tecnica
+
+ participations_testers = Participacion.objects.exclude(
+ catador=participation_current_tester.catador).filter(tecnica=technique)
+
+ all_testers = [
+ participation.catador for participation in participations_testers]
+
+ list_words_testers = list(ListaPalabras.objects.filter(
+ tecnica=technique, catador__in=all_testers, es_final=True))
+
+ if not list_words_testers:
+ return JsonResponse({"error": "Aun no hay listas finales de Catadores"})
+
+ result = []
+ for list_tester in list_words_testers:
+ try:
+ username = list_tester.catador.user.username
+ except Exception:
+ username = None
+
+ finish = [
+ participation.finalizado for participation in participations_testers if participation.catador.user.username == username][0]
+
+ status = "Lista terminada" if finish else "Lista en proceso"
+
+ words_qs = list_tester.palabras.all()
+ words = []
+ for p in words_qs:
+ nombre = getattr(p, 'nombre_palabra', None)
+ words.append({
+ 'id': getattr(p, 'id', None),
+ 'nombre_palabra': nombre
+ })
+
+ result.append({
+ 'username': username,
+ 'words': words,
+ 'status': status
+ })
+
+ return JsonResponse({
+ "message": "Listas encontradas",
+ "lists_words": result
+ })
+
+ @staticmethod
+ def saveList(request: HttpRequest, words: list, current_phase: int):
+ dic_words = {}
+ for index, word in enumerate(words, start=1):
+ dic_words[f"palabra_{index}"] = word
+
+ form = ListWordsForm(dic_words, new_words=words)
+
+ if form.is_valid():
+ participation = Participacion.objects.get(
+ id=request.session["id_participation"])
+
+ if not participation:
+ return JsonResponse({"error": "No está autorizado en la sesión"})
+
+ technique = participation.tecnica
+
+ list_words_tester: ListaPalabras
+ if current_phase == 1:
+ (list_words_tester, created) = ListaPalabras.objects.get_or_create(
+ tecnica=technique,
+ catador=request.user.user_catador,
+ es_final=False,
+ )
+ elif current_phase == 2:
+ (list_words_tester, created) = ListaPalabras.objects.get_or_create(
+ tecnica=technique,
+ catador=request.user.user_catador,
+ es_final=True,
+ )
+
+ added_words = RatingPFListController.addWordsToListWordsTester(
+ list_words=words, list_tester=list_words_tester)
+
+ response = JsonResponse({
+ "message": "Palabras guardadas con exito",
+ "words": [word.nombre_palabra for word in added_words]
+ })
+ else:
+ response = JsonResponse({"error": "Palabras invalidas"})
+
+ return response
+
+ @staticmethod
+ def addWordsToListWordsTester(list_words: list[str], list_tester: ListaPalabras):
+ # Normalizar
+ clean_words = [s.strip() for s in list_words if s.strip()]
+
+ # Obtener existentes
+ all_words = Palabra.objects.filter(nombre_palabra__in=clean_words)
+
+ names_words_exist = set(
+ all_words.values_list('nombre_palabra', flat=True))
+
+ # Ejecutar el query para no sumar palabras repetidas
+ all_words = list(all_words)
+
+ # Determinar faltantes
+ missing_words = [
+ nombre for nombre in clean_words if nombre not in names_words_exist]
+ print("No save words", missing_words)
+
+ created_words = []
+
+ # Intentar crear missing_words
+ for nombre in missing_words:
+ try:
+ with transaction.atomic():
+ palabra, created = Palabra.objects.get_or_create(
+ nombre_palabra=nombre)
+ if created:
+ created_words.append(palabra)
+ except IntegrityError:
+ palabra = Palabra.objects.get(nombre_palabra=nombre)
+ created_words.append(palabra)
+
+ # Combinar todas (all_words + created_words)
+ all_new_words = all_words + created_words
+
+ list_tester.palabras.set(all_new_words)
+
+ return all_new_words
+
+ @staticmethod
+ def saveRatings(request: HttpRequest, word_rating: str, data: list):
+ participation = Participacion.objects.get(
+ id=request.session["id_participation"])
+
+ technique = participation.tecnica
+ products = Producto.objects.filter(id_tecnica=technique)
+
+ # Crear o obtener las instancias Calificacion en la repeticion de todos los producutos
+ ratings = Calificacion.objects.filter(
+ num_repeticion=technique.repeticion,
+ id_tecnica=technique,
+ id_catador=participation.catador,
+ id_producto__in=products
+ )
+
+ existing_dict = {rating.id_producto_id: rating for rating in ratings}
+ to_create = []
+
+ for product in products:
+ if product.id not in existing_dict:
+ to_create.append(
+ Calificacion(
+ num_repeticion=technique.repeticion,
+ id_producto=product,
+ id_tecnica=technique,
+ id_catador=participation.catador
+ )
+ )
+ Calificacion.objects.bulk_create(to_create)
+
+ ratings = Calificacion.objects.filter(
+ num_repeticion=technique.repeticion,
+ id_tecnica=technique,
+ id_catador=participation.catador,
+ id_producto__in=products
+ )
+
+ # Guardar datos con ValorDecimal
+ word = Palabra.objects.get(nombre_palabra=word_rating)
+ try:
+ if (
+ len(data) != len(products) or
+ len(data) != len(ratings) or
+ len(products) != len(ratings)
+ ):
+ raise ValueError(
+ "Al parecer los datos mandados no corresponden con el total de productos")
+
+ product_values = {info["product"]["code"]: info["value"] for info in data}
+
+ datos_to_create = []
+ values_to_create = []
+
+ with transaction.atomic():
+ for rating in ratings:
+ datos_to_create.append(Dato(
+ id_palabra=word,
+ id_calificacion=rating
+ ))
+
+ datos_save = Dato.objects.bulk_create(datos_to_create)
+ save_data = Dato.objects.filter(id_palabra=word, id_calificacion__in=ratings)
+
+ for data_save in save_data:
+ values_to_create.append(ValorDecimal(
+ id_dato=data_save,
+ valor=product_values.get(data_save.id_calificacion.id_producto.codigoProducto)
+ ))
+
+ ValorDecimal.objects.bulk_create(values_to_create)
+
+ return JsonResponse({"message": "Calificaciones guardadas con exito"})
+ except ValueError as e:
+ error_message = str(e)
+ print(f"Error de calificacion: {error_message}")
+ return JsonResponse({"error": error_message})
diff --git a/tecnicas/controllers/views_controller/api_rating_controller.py b/tecnicas/controllers/api_controller/rating_sacales_controller.py
similarity index 89%
rename from tecnicas/controllers/views_controller/api_rating_controller.py
rename to tecnicas/controllers/api_controller/rating_sacales_controller.py
index e65014b2c618238105db13e494fb5c7dcbbc52d2..e2e4948c9e3ef6cdfdc859aef4a9c44658c89908 100644
--- a/tecnicas/controllers/views_controller/api_rating_controller.py
+++ b/tecnicas/controllers/api_controller/rating_sacales_controller.py
@@ -1,8 +1,8 @@
-from ...controllers import CalificacionController, DatoController
+from .. import CalificacionController, DatoController
from ...utils import controller_error
-class ApiRatingController():
+class RatingScalesController():
def __init__(self, rating_controller: CalificacionController, data_controller: DatoController):
self.rating_controller = rating_controller
self.data_controller = data_controller
diff --git a/tecnicas/controllers/api_controller/rating_sort_controller.py b/tecnicas/controllers/api_controller/rating_sort_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..c7f105bbabb19f9b7ab091596c55a144179301c9
--- /dev/null
+++ b/tecnicas/controllers/api_controller/rating_sort_controller.py
@@ -0,0 +1,67 @@
+from django.http import JsonResponse, HttpRequest
+from django.db import transaction
+from tecnicas.models import Participacion, Palabra, GrupoProducto, Producto
+
+
+class RatingSortController():
+ def __init__(self):
+ pass
+
+ @staticmethod
+ def saveRating(request: HttpRequest, data: list[dict]):
+ try:
+ with transaction.atomic():
+ participation = Participacion.objects.get(
+ id=request.session["id_participation"])
+
+ technique = participation.tecnica
+ catador = participation.catador
+
+ # Obtener productos de la técnica
+ technique_products = Producto.objects.filter(
+ id_tecnica=technique)
+ technique_product_ids = set(p.id for p in technique_products)
+
+ # Recolectar IDs de productos enviados
+ sent_product_ids = set()
+ for group in data:
+ for product in group["products"]:
+ sent_product_ids.add(int(product["id"]))
+
+ # Validar que los productos enviados existan en la técnica
+ if not sent_product_ids.issubset(technique_product_ids):
+ return JsonResponse({"error": "Productos enviados no pertenecen a la técnica"})
+
+ # Validar que todos los productos de la técnica estén presentes
+ if sent_product_ids != technique_product_ids:
+ return JsonResponse({"error": "Faltan productos por clasificar"})
+
+ for group in data:
+ words_data = group["words"]
+ products_data = group["products"]
+
+ # Crear u obtener palabras
+ words_objs = []
+ for word_name in words_data:
+ word, created = Palabra.objects.get_or_create(nombre_palabra=word_name)
+ words_objs.append(word)
+
+ # Crear GrupoProducto
+ group_product = GrupoProducto.objects.create(
+ tecnica=technique,
+ catador=catador
+ )
+
+ # Asignar palabras
+ group_product.palabras.set(words_objs)
+
+ # Asignar productos
+ product_ids = [p["id"] for p in products_data]
+ group_product.productos.set(product_ids)
+
+ return JsonResponse({"message": "Valores guardados"})
+ except Participacion.DoesNotExist:
+ return JsonResponse({"error": "Participación no encontrada"})
+ except Exception as e:
+ print(f"Error de calificacion: {e}")
+ return JsonResponse({"error": "Error al guardar los datos"})
diff --git a/tecnicas/controllers/models_controller/calificacion_controller.py b/tecnicas/controllers/models_controller/calificacion_controller.py
index 5e21f3ccc03122a99ebbe41c42a375a2343246e0..66e51b1521732fb537cea3abaad56c4e0597d053 100644
--- a/tecnicas/controllers/models_controller/calificacion_controller.py
+++ b/tecnicas/controllers/models_controller/calificacion_controller.py
@@ -21,7 +21,7 @@ class CalificacionController():
@staticmethod
def getRatingsByTechnique(technique: Tecnica):
repetition = technique.repeticion
- ratings = list(Calificacion.objects.filter(id_tecnica=technique))
+ ratings = list(Calificacion.objects.filter(id_tecnica=technique, num_repeticion=repetition))
return ratings
@staticmethod
diff --git a/tecnicas/controllers/models_controller/dato_controller.py b/tecnicas/controllers/models_controller/dato_controller.py
index 9afcc498d6458644344f187b4bb68c9d6c74a608..524c324127308d9aa75b81e1559327ae6c426930 100644
--- a/tecnicas/controllers/models_controller/dato_controller.py
+++ b/tecnicas/controllers/models_controller/dato_controller.py
@@ -1,5 +1,5 @@
-from ...models import Calificacion, Dato, Palabra, ValorDecimal, ValorBooleano, Tecnica
-from ...utils import controller_error, getId
+from tecnicas.models import Calificacion, Dato, Palabra, ValorDecimal, ValorBooleano, Tecnica, Catador
+from tecnicas.utils import controller_error, getId
from django.core.exceptions import ValidationError
from django.db.models import F
@@ -12,11 +12,7 @@ class DatoController():
}
self.data = Dato(**atributes)
-
- if isinstance(value_rating, bool):
- self.value_data = ValorBooleano(valor=value_rating)
- else:
- self.value_data = ValorDecimal(valor=value_rating)
+ self.value_rating = value_rating
def setRating(self, new_rating: Calificacion):
try:
@@ -39,15 +35,23 @@ class DatoController():
except ValidationError as e:
return controller_error(e.message)
- def setValue(self, new_value=None):
- if new_value:
- if isinstance(new_value, bool):
- self.value_data = ValorBooleano(valor=new_value)
- else:
- self.value_data = ValorDecimal(valor=new_value)
+ def setValue(self):
+ type_technique = self.data.id_calificacion.id_tecnica.tipo_tecnica
+ if type_technique == "cata":
+ self.value_data = ValorBooleano(valor=self.value_rating)
+
else:
- self.value_data.id_dato = self.data
+ type_scale = self.data.id_calificacion.id_tecnica.escala_tecnica.id_tipo_escala.nombre_escala
+
+ if type_scale == "continua":
+ decimal_value = self.value_rating/100
+ value_rounded = round(decimal_value)
+ self.value_data = ValorDecimal(valor=value_rounded)
+ else:
+ self.value_data = ValorDecimal(valor=self.value_rating)
+
+ self.value_data.id_dato = self.data
return self.value_data
def saveValue(self):
@@ -94,3 +98,21 @@ class DatoController():
)
return list(result)
+
+ @staticmethod
+ def getWordValuesPF(technique: Tecnica, ratings: list[Calificacion], tester: Catador):
+ ids_ratings = [rat.id for rat in ratings]
+
+ result = (
+ ValorDecimal.objects
+ .filter(id_dato__id_calificacion_id__in=ids_ratings, id_dato__id_calificacion__id_catador=tester)
+ .values(
+ nombre_palabra=F("id_dato__id_palabra__nombre_palabra"),
+ repeticion=F("id_dato__id_calificacion__num_repeticion"),
+ producto_code=F(
+ "id_dato__id_calificacion__id_producto__codigoProducto"),
+ dato_valor=F("valor")
+ )
+ )
+
+ return list(result)
diff --git a/tecnicas/controllers/models_controller/escala_controller.py b/tecnicas/controllers/models_controller/escala_controller.py
index 89a7b50c388b7f6d36f1d8ec694284ed957210dc..7b0ee1f453086ef8c4b5ff4c820bbdd30a7f89d8 100644
--- a/tecnicas/controllers/models_controller/escala_controller.py
+++ b/tecnicas/controllers/models_controller/escala_controller.py
@@ -7,19 +7,25 @@ class EscalaController():
scale: Escala
tags_relation: dict[str, EtiquetasEscala]
- def __init__(self, data):
- self.scale = Escala(
- id_tipo_escala=TipoEscala.objects.get(id=data["id_scale"]),
- longitud=data["size"],
- tecnica=data["technique"]
- )
-
- def setScale(self, newData):
- self.scale = Escala(
- id_tipo_escala=TipoEscala.objects.get(id=newData["id_scale"]),
- longitud=newData["size"],
- tecnica=newData["technique"]
- )
+ def __init__(self, data, use_scale: Escala = None):
+ if use_scale:
+ self.scale = use_scale
+ else:
+ self.scale = Escala(
+ id_tipo_escala=TipoEscala.objects.get(id=data["id_scale"]),
+ longitud=data["size"],
+ tecnica=data["technique"]
+ )
+
+ def setScale(self, newData, use_scale: Escala):
+ if use_scale:
+ self.scale = use_scale
+ else:
+ self.scale = Escala(
+ id_tipo_escala=TipoEscala.objects.get(id=data["id_scale"]),
+ longitud=data["size"],
+ tecnica=data["technique"]
+ )
def saveScale(self):
try:
diff --git a/tecnicas/controllers/models_controller/particiapacion_controller.py b/tecnicas/controllers/models_controller/particiapacion_controller.py
index a902b0dbe9c9da75248eae1280820f32a1b72b22..0acfb707d84b89d5c9edf1152c3247690c32355b 100644
--- a/tecnicas/controllers/models_controller/particiapacion_controller.py
+++ b/tecnicas/controllers/models_controller/particiapacion_controller.py
@@ -35,24 +35,14 @@ class ParticipacionController():
return controller_error("No se ha encontrado la participación")
@staticmethod
- def outAllInSession(session: SesionSensorial | str):
+ def outAllInSession(session: SesionSensorial):
try:
- if isinstance(session, str):
- use_session = SesionSensorial.objects.get(
- codigo_sesion=session)
- else:
- use_session = session
-
participations = Participacion.objects.filter(
- tecnica=use_session.tecnica)
-
- if not participations.exists():
- message = "No se encontraron participaciones en la sesión"
- return (False, message)
+ tecnica=session.tecnica)
participations.update(finalizado=False)
- message = "Participaciones actualizadas a finalizadas"
+ message = "Participaciones actualizadas a finalizadas como falso"
return (True, message)
except Exception as e:
print(f"Error al actualizar las participaciones: {str(e)}")
diff --git a/tecnicas/controllers/models_controller/sesion_controller.py b/tecnicas/controllers/models_controller/sesion_controller.py
index 0c5f7547577cb157cbd8b266c394d6fd1355919b..be0cea2fdde136aaef928089e3dc46d6c66d3503 100644
--- a/tecnicas/controllers/models_controller/sesion_controller.py
+++ b/tecnicas/controllers/models_controller/sesion_controller.py
@@ -125,12 +125,7 @@ class SesionController():
return controller_error("Presentador invalido")
@staticmethod
- def finishRepetion(session: SesionSensorial | str):
- if isinstance(session, str):
- use_session = SesionSensorial.objects.get(codigo_sesion=session)
- else:
- use_session = session
-
- use_session.activo = False
- use_session.save()
+ def finishRepetion(session: SesionSensorial):
+ session.activo = False
+ session.save()
return session
diff --git a/tecnicas/controllers/models_controller/tecnica_controller.py b/tecnicas/controllers/models_controller/tecnica_controller.py
index 71265258841e5c3bcbc1843d6438bfa749275262..30403356cf310c8ed2b1bf57790a2d140657eafb 100644
--- a/tecnicas/controllers/models_controller/tecnica_controller.py
+++ b/tecnicas/controllers/models_controller/tecnica_controller.py
@@ -16,7 +16,7 @@ class TecnicaController():
def setTechniqueFromBasicData(self, basic):
self.technique = Tecnica(
tipo_tecnica=TipoTecnica.objects.get(nombre_tecnica=basic["name_tecnica"]),
- id_estilo=EstiloPalabra.objects.get(id=basic["estilo_palabras"]),
+ id_estilo=EstiloPalabra.objects.get(nombre_estilo=basic["estilo_palabras"]),
repeticiones_max=basic["numero_repeticiones"] or 1,
limite_catadores=basic["numero_catadores"],
instrucciones=basic["instrucciones"] or "Espere instrucciones del Presentador",
diff --git a/tecnicas/controllers/views_controller/create_session/panel_basic_controller.py b/tecnicas/controllers/views_controller/create_session/panel_basic_controller.py
index b2fc94263a354656a6ede862fb5d32151f752f1f..ff5aef6023629a56c115dff41cc7b6259696c469 100644
--- a/tecnicas/controllers/views_controller/create_session/panel_basic_controller.py
+++ b/tecnicas/controllers/views_controller/create_session/panel_basic_controller.py
@@ -1,4 +1,4 @@
-from tecnicas.forms import SesionBasicForm, SesionBasicCATAForm
+from tecnicas.forms import SesionBasicForm, SesionBasicCATAForm, SesionBasicPFForm, SesionBasicSortForm, SesionBasicNappingForm
from django.http import HttpRequest
from django.shortcuts import redirect, render
from django.urls import reverse
@@ -10,11 +10,14 @@ class PanelBasicController():
"numero_repeticiones": 1
}
- url_panel_basic = "tecnicas/create_sesion/configuracion-panel-basic.html"
+ url_panel_basic = "tecnicas/create_sesion/conf-panel-basic.html"
url_panel_basic_cata = "tecnicas/create_sesion/panel-basic-cata.html"
+ url_panel_basic_pf = "tecnicas/create_sesion/panel-basic-pf.html"
+ url_panel_basic_sort = "tecnicas/create_sesion/panel-basic-sort.html"
+ url_panel_basic_napping = "tecnicas/create_sesion/panel-basic-napping.html"
- url_next_panel_scales = "cata_system:panel_configuracion_tags"
- url_next_panel_cata = "cata_system:panel_configuracion_codes"
+ url_next_panel_tags = "cata_system:panel_configuracion_tags"
+ url_next_panel_codes = "cata_system:panel_configuracion_codes"
url_select_technique = "cata_system:seleccion_tecnica"
@@ -42,7 +45,7 @@ class PanelBasicController():
if form.is_valid():
values = {}
for name, value in form.cleaned_data.items():
- if name == "estilo_palabras" or name == "tipo_escala":
+ if name == "tipo_escala":
values[name] = value.id
else:
values[name] = value
@@ -50,7 +53,7 @@ class PanelBasicController():
values["name_tecnica"] = name_tecnica
request.session['form_basic'] = values
response = redirect(
- reverse("cata_system:panel_configuracion_tags"))
+ reverse(PanelBasicController.url_next_panel_tags))
else:
response = render(request, PanelBasicController.url_panel_basic, {
"form_sesion": form, "error": "Información no valida"})
@@ -83,7 +86,7 @@ class PanelBasicController():
if form.is_valid():
values = {}
for name, value in form.cleaned_data.items():
- if name == "estilo_palabras" or name == "tipo_escala":
+ if name == "tipo_escala":
values[name] = value.id
else:
values[name] = value
@@ -102,7 +105,7 @@ class PanelBasicController():
values["name_tecnica"] = name_tecnica
request.session['form_basic'] = values
response = redirect(
- reverse("cata_system:panel_configuracion_tags"))
+ reverse(PanelBasicController.url_next_panel_tags))
else:
response = render(request, PanelBasicController.url_panel_basic, {
"form_sesion": form, "error": "Información no valida"})
@@ -131,17 +134,107 @@ class PanelBasicController():
if form.is_valid():
values = {}
for name, value in form.cleaned_data.items():
- if name == "estilo_palabras":
- values[name] = value.id
- else:
- values[name] = value
+ values[name] = value
values["name_tecnica"] = name_tecnica
request.session['form_basic'] = values
response = redirect(
- reverse(PanelBasicController.url_next_panel_cata))
+ reverse(PanelBasicController.url_next_panel_codes))
else:
response = render(request, PanelBasicController.url_panel_basic, {
"form_sesion": form, "error": "Información no valida"})
return response
+
+ @staticmethod
+ def controllGetPF(request: HttpRequest):
+ form_sesion = SesionBasicPFForm()
+
+ view_context = {
+ "form_sesion": form_sesion,
+ "use_technique": "perfil flash"
+ }
+
+ return render(
+ request, PanelBasicController.url_panel_basic_pf, view_context)
+
+ @staticmethod
+ def controllPostPF(request: HttpRequest, name_tecnica: str):
+ form = SesionBasicPFForm(request.POST)
+
+ if form.is_valid():
+ values = {}
+ for name, value in form.cleaned_data.items():
+ values[name] = value
+
+ values["name_tecnica"] = name_tecnica
+ request.session['form_basic'] = values
+ response = redirect(
+ reverse(PanelBasicController.url_next_panel_codes))
+ else:
+ response = render(request, PanelBasicController.url_panel_basic_pf, {
+ "form_sesion": form, "error": "Información no valida"})
+
+ return response
+
+ @staticmethod
+ def controllGetSort(request: HttpRequest):
+ form_sesion = SesionBasicSortForm()
+
+ view_context = {
+ "form_sesion": form_sesion,
+ "use_technique": "sort"
+ }
+
+ return render(
+ request, PanelBasicController.url_panel_basic_sort, view_context)
+
+ @staticmethod
+ def controllPostSort(request: HttpRequest, name_tecnica: str):
+ form = SesionBasicSortForm(request.POST)
+
+ if form.is_valid():
+ values = {}
+ for name, value in form.cleaned_data.items():
+ values[name] = value
+
+ values["name_tecnica"] = name_tecnica
+ request.session['form_basic'] = values
+ response = redirect(
+ reverse(PanelBasicController.url_next_panel_codes))
+ else:
+ response = render(request, PanelBasicController.url_panel_basic_sort, {
+ "form_sesion": form, "error": "Información no valida"})
+
+ return response
+
+ @staticmethod
+ def controllGetNapping(request: HttpRequest):
+ form_sesion = SesionBasicNappingForm()
+
+ view_context = {
+ "form_sesion": form_sesion,
+ "use_technique": "napping"
+ }
+
+ return render(
+ request, PanelBasicController.url_panel_basic_napping, view_context)
+
+ @staticmethod
+ def controllPostNapping(request: HttpRequest, name_tecnica: str):
+ form = SesionBasicNappingForm(request.POST)
+
+ if form.is_valid():
+ values = {}
+ for name, value in form.cleaned_data.items():
+ values[name] = value
+
+ values["name_tecnica"] = name_tecnica
+ request.session['form_basic'] = values
+ response = redirect(
+ reverse(PanelBasicController.url_next_panel_codes))
+ else:
+ response = render(request, PanelBasicController.url_panel_basic_napping, {
+ "form_sesion": form, "error": "Información no valida"})
+
+ return response
diff --git a/tecnicas/controllers/views_controller/create_session/panel_codes_controller.py b/tecnicas/controllers/views_controller/create_session/panel_codes_controller.py
index 96ac24f4636d9b26291f8fe004f279b48aae6ab6..f02b9f57d813618b49bbecc87f92a0457b55873b 100644
--- a/tecnicas/controllers/views_controller/create_session/panel_codes_controller.py
+++ b/tecnicas/controllers/views_controller/create_session/panel_codes_controller.py
@@ -7,8 +7,9 @@ import json
class PanelCodesController():
- url_current_panel = "tecnicas/create_sesion/configuracion-panel-codes.html"
- url_next_panel = "cata_system:panel_configuracion_words"
+ url_current_panel = "tecnicas/create_sesion/conf-panel-codes.html"
+ url_words = "cata_system:panel_configuracion_words"
+ url_create_session = "cata_system:creando_sesion"
def __init__(self):
pass
@@ -58,14 +59,14 @@ class PanelCodesController():
codes_sort["sort_codes"] = sorts_code
request.session["form_codes"] = codes_sort
- return redirect(reverse(PanelCodesController.url_next_panel))
+ return redirect(reverse(PanelCodesController.url_words))
else:
context_codes_form["error"] = "error en los datos recibidos"
return render(request, PanelCodesController.url_current_panel, context_codes_form)
@staticmethod
- def controllGetRATA(request: HttpRequest, data):
+ def controllGetWithoutOrders(request: HttpRequest, data, name_technique: str):
num_products = data["numero_productos"]
codes_products = generarCodigos(num_products)
form_codes = CodesForm(codes=codes_products)
@@ -73,13 +74,13 @@ class PanelCodesController():
context_codes_form = {
"form_codes": form_codes,
"num_tester": 0,
- "use_technique": "rata"
+ "use_technique": name_technique
}
return render(request, PanelCodesController.url_current_panel, context_codes_form)
@staticmethod
- def controllPostRATA(request: HttpRequest, is_rata: True):
+ def controllPostWithWords(request: HttpRequest, name_technique: str):
codes = []
context_codes_form = {}
@@ -91,12 +92,14 @@ class PanelCodesController():
context_codes_form = {
"form_codes": form_codes,
- "use_technique": "rata" if is_rata else "cata"
+ "use_technique": name_technique
}
if form_codes.is_valid():
- request.session["form_codes"] = codes
- return redirect(reverse(PanelCodesController.url_next_panel))
+ # Extract codes from cleaned_data to ensure uppercase conversion
+ cleaned_codes = [value for name, value in form_codes.cleaned_data.items() if name.startswith('producto_')]
+ request.session["form_codes"] = cleaned_codes
+ return redirect(reverse(PanelCodesController.url_words))
else:
context_codes_form["error"] = "error en los datos recibidos"
@@ -114,3 +117,29 @@ class PanelCodesController():
}
return render(request, PanelCodesController.url_current_panel, context_codes_form)
+
+ @staticmethod
+ def controllPostWithoutOrdersWords(request: HttpRequest, name_technique: str):
+ codes = []
+ context_codes_form = {}
+
+ for name, value in request.POST.items():
+ if name.__contains__("producto_"):
+ codes.append(value)
+
+ form_codes = CodesForm(request.POST, codes=codes)
+
+ context_codes_form = {
+ "form_codes": form_codes,
+ "use_technique": name_technique
+ }
+
+ if form_codes.is_valid():
+ # Extract codes from cleaned_data to ensure uppercase conversion
+ cleaned_codes = [value for name, value in form_codes.cleaned_data.items() if name.startswith('producto_')]
+ request.session["form_codes"] = cleaned_codes
+ return redirect(reverse(PanelCodesController.url_create_session))
+ else:
+ context_codes_form["error"] = "error en los datos recibidos"
+
+ return render(request, PanelCodesController.url_current_panel, context_codes_form)
diff --git a/tecnicas/controllers/views_controller/create_session/panel_create_controller.py b/tecnicas/controllers/views_controller/create_session/panel_create_controller.py
deleted file mode 100644
index dbab342d25a99d33a73c48615208300b7bdfcd51..0000000000000000000000000000000000000000
--- a/tecnicas/controllers/views_controller/create_session/panel_create_controller.py
+++ /dev/null
@@ -1,446 +0,0 @@
-from django.http import HttpRequest, JsonResponse
-from django.db import transaction
-from django.shortcuts import render
-from tecnicas.utils import general_error
-from tecnicas.models import EsAtributo, EsVocabulario, Vocabulario, Tecnica, TipoTecnica, EstiloPalabra, Producto, Palabra, SesionSensorial
-from tecnicas.controllers import TecnicaController, EscalaController, ProductosController, OrdenesController, EstiloPalabrasController, PalabrasController, SesionController
-from tecnicas.utils import deleteDataSession
-
-
-class PanelCreateController():
- def __init__(self):
- pass
-
- @staticmethod
- def controllGetEscalas(request: HttpRequest):
- return render(
- request, 'tecnicas/create_sesion/creando_sesion.html')
-
- @staticmethod
- def controllPostEscalas(request: HttpRequest):
- if request.POST.get('action') == 'create_session':
- if not request.session.get("form_tags") or not request.session.get("form_codes") or not request.session.get("form_words"):
- deleteDataSession(request)
- return general_error("No se ha especificado información necesaria para la creación de la sesión, por favor, vuelve a intentarlo")
- try:
- with transaction.atomic():
- # ////////////////////////////////////////////////////// #
- #
- # First step: Create technique and scale with their tags #
- #
- # ////////////////////////////////////////////////////// #
- data_basic = request.session["form_basic"]
- controllerTechnique = TecnicaController()
- controllerTechnique.setTechniqueFromBasicData(
- basic=data_basic)
- technique = controllerTechnique.saveTechnique()
- if not technique:
- raise ValueError("Error al guardar la técnica")
-
- data_scale = {
- "id_scale": data_basic["tipo_escala"],
- "size": data_basic["tamano_escala"],
- "technique": technique
- }
-
- controllerScale = EscalaController(data=data_scale)
-
- scale = controllerScale.saveScale()
- if isinstance(scale, dict):
- raise ValueError(scale["error"])
-
- dict_tags = request.session["form_tags"]
- saved_related_tags = controllerScale.realteTags(dict_tags)
- if "error" in saved_related_tags:
- raise ValueError(saved_related_tags["error"])
-
- # ////////////////////////////////////////////////////////// #
- #
- # Second step: Create orders, productos and set the position #
- #
- # ////////////////////////////////////////////////////////// #
- data_codes = request.session["form_codes"]
-
- list_codes_dict = data_codes["product_codes"]
-
- codes = []
- for product in list_codes_dict:
- code = next(iter(product.values()))
- codes.append(code)
-
- controllerProducts = ProductosController(
- codes=codes,
- technique=technique
- )
-
- controllerProducts.setProductsNoSave()
- saved_prodcuts = controllerProducts.saveProducts()
- if isinstance(saved_prodcuts, dict):
- raise ValueError(saved_prodcuts["error"])
-
- raw_sort_codes = data_codes["sort_codes"]
- controllerOrdes = OrdenesController(
- raw_orders=raw_sort_codes,
- list_products=saved_prodcuts,
- technique=technique
- )
-
- controllerOrdes.setOrdersToSave()
- saved_orders = controllerOrdes.saveOrders()
- if isinstance(saved_orders, dict):
- raise ValueError(saved_orders["error"])
-
- seded_positions = controllerOrdes.setPositions()
- if isinstance(seded_positions, dict):
- raise ValueError(seded_positions["error"])
-
- saved_postions = controllerOrdes.savePositions()
- if isinstance(saved_postions, dict):
- raise ValueError(saved_prodcuts["error"])
-
- # /////////////////////////////////////////////////////// #
- #
- # Third step: Create relations technique with Words Style #
- #
- # /////////////////////////////////////////////////////// #
- style_words = technique.id_estilo.nombre_estilo
- if style_words == "atributos":
- ids_words = request.session["form_words"]
- words_controller = PalabrasController(ids=ids_words)
-
- words_to_use = words_controller.setWords()
- if isinstance(words_to_use, dict):
- raise ValueError(words_to_use["error"])
-
- style_controller = EstiloPalabrasController(
- technique=technique, words=words_to_use)
-
- instace_style = style_controller.createAndSaveInstaceStyle()
- if isinstance(instace_style, dict):
- raise ValueError(instace_style["error"])
-
- words_using = style_controller.relatedWords()
- if isinstance(words_using, dict):
- raise ValueError(words_using["error"])
- elif style_words == "vocabulario":
- name_vocabulary = request.session["form_words"]
- vocabulary = Vocabulario.objects.get(
- nombre_vocabulario=name_vocabulary)
-
- es_vocabulary = EsVocabulario.objects.create(
- id_tecnica=technique,
- id_vocabulario=vocabulary
- )
- else:
- raise ValueError("Estilo de palabas no permitido")
-
- # //////////////////////////////////////////////////////// #
- #
- # Fourth step: Create session and relat with the technique #
- #
- # //////////////////////////////////////////////////////// #
- session_controller = SesionController(
- name_session=data_basic["nombre_sesion"] if data_basic["nombre_sesion"] != "" else None,
- technique=technique,
- creator=request.user.user_presentador
- )
-
- setting_session = session_controller.setSession()
- if isinstance(setting_session, dict):
- raise ValueError(setting_session["error"])
-
- saved_session = session_controller.saveSession()
- if isinstance(saved_session, dict):
- raise ValueError(saved_session["error"])
-
- context = {
- "message": "sesión creada",
- "data": {
- "codigo_sesion": saved_session.codigo_sesion,
- "nombre_sesion": saved_session.nombre_sesion
- }
- }
-
- # ////////////////////////////////// #
- #
- # Final step: Delete date in session #
- #
- # ////////////////////////////////// #
-
- deleteDataSession(request)
- return JsonResponse(context)
- except ValueError as e:
- return general_error(f"Error: {e}")
- else:
- return general_error("No se ha establecido acción")
-
- @staticmethod
- def controllPostRATA(request: HttpRequest):
- if request.POST.get('action') == 'create_session':
- if not request.session.get("form_tags") or not request.session.get("form_codes") or not request.session.get("form_words"):
- deleteDataSession(request)
- return general_error("No se ha especificado información necesaria para la creación de la sesión, por favor, vuelve a intentarlo")
- try:
- with transaction.atomic():
- # ////////////////////////////////////////////////////// #
- #
- # First step: Create technique and scale with their tags #
- #
- # ////////////////////////////////////////////////////// #
- data_basic = request.session["form_basic"]
- data_basic["numero_catadores"] = 0
- data_basic["numero_repeticiones"] = 1
-
- technique = Tecnica.objects.create(
- tipo_tecnica=TipoTecnica.objects.get(
- nombre_tecnica=data_basic["name_tecnica"]),
- id_estilo=EstiloPalabra.objects.get(
- id=data_basic["estilo_palabras"]),
- repeticiones_max=data_basic["numero_repeticiones"] or 1,
- limite_catadores=data_basic["numero_catadores"],
- instrucciones=data_basic["instrucciones"] or "Espere instrucciones del Presentador",
- )
-
- if not technique:
- raise ValueError("Error al guardar la técnica")
-
- data_scale = {
- "id_scale": data_basic["tipo_escala"],
- "size": data_basic["tamano_escala"],
- "technique": technique
- }
-
- controllerScale = EscalaController(data=data_scale)
-
- scale = controllerScale.saveScale()
- if isinstance(scale, dict):
- raise ValueError(scale["error"])
-
- dict_tags = request.session["form_tags"]
- saved_related_tags = controllerScale.realteTags(dict_tags)
- if "error" in saved_related_tags:
- raise ValueError(saved_related_tags["error"])
-
- # ////////////////////////////////////////////// #
- #
- # Second step: Create productos with their codes #
- #
- # ////////////////////////////////////////////// #
- codes = request.session["form_codes"]
-
- if not codes:
- raise ValueError("No hay códigos de productos")
-
- products_without_save = []
- for code in codes:
- product = Producto(
- codigoProducto=code,
- id_tecnica=technique
- )
- products_without_save.append(product)
-
- Producto.objects.bulk_create(products_without_save)
-
- # /////////////////////////////////////////////////////// #
- #
- # Third step: Create relations technique with Words Style #
- #
- # /////////////////////////////////////////////////////// #
- style_words = technique.id_estilo.nombre_estilo
-
- if style_words == "atributos":
- raw_ids_words = request.session["form_words"]
- ids_words = [int(id_w) for id_w in raw_ids_words]
-
- words = Palabra.objects.filter(id__in=ids_words)
-
- style_atribute = EsAtributo.objects.create(
- id_tecnica=technique
- )
-
- if not style_atribute:
- raise ValueError(
- "Error al intentar relacionar las palabras con la técnica")
-
- style_atribute.palabras.set(words)
-
- elif style_words == "vocabulario":
- name_vocabulary = request.session["form_words"]
- try:
- vocabulary = Vocabulario.objects.get(
- nombre_vocabulario=name_vocabulary)
- except Vocabulario.DoesNotExist:
- raise ValueError("Vocabulario no encontrado")
-
- es_vocabulary = EsVocabulario.objects.create(
- id_tecnica=technique,
- id_vocabulario=vocabulary
- )
- if not es_vocabulary:
- raise ValueError(
- "Error al intentar relacionar el vocabulario con la técnica")
-
- else:
- raise ValueError("Estilo de palabas no permitido")
-
- # //////////////////////////////////////////////////////// #
- #
- # Fourth step: Create session and relat with the technique #
- #
- # //////////////////////////////////////////////////////// #
- session = SesionSensorial.objects.create(
- name_session=data_basic["nombre_sesion"] if data_basic["nombre_sesion"] != "" else None,
- technique=technique,
- creator=request.user.user_presentador
- )
-
- if not session:
- raise ValueError("Error al crear sesion sensorial")
-
- context = {
- "message": "sesión creada",
- "data": {
- "codigo_sesion": session.codigo_sesion,
- "nombre_sesion": session.nombre_sesion
- }
- }
-
- # ////////////////////////////////// #
- #
- # Final step: Delete date en session #
- #
- # ////////////////////////////////// #
-
- deleteDataSession(request)
- return JsonResponse(context)
- except ValueError as e:
- return general_error(f"Error: {e}")
- else:
- return general_error("No se ha establecido acción")
-
- @staticmethod
- def controllPostCATA(request: HttpRequest):
- if request.POST.get('action') == 'create_session':
- if not request.session.get("form_codes") or not request.session.get("form_words"):
- deleteDataSession(request)
- return general_error("No se ha especificado información necesaria para la creación de la sesión, por favor, vuelve a intentarlo")
- try:
- with transaction.atomic():
- # //////////////////////////// #
- #
- # First step: Create technique #
- #
- # //////////////////////////// #
- data_basic = request.session["form_basic"]
- data_basic["numero_catadores"] = 0
- data_basic["numero_repeticiones"] = 1
-
- technique = Tecnica.objects.create(
- tipo_tecnica=TipoTecnica.objects.get(
- nombre_tecnica=data_basic["name_tecnica"]),
- id_estilo=EstiloPalabra.objects.get(
- id=data_basic["estilo_palabras"]),
- repeticiones_max=data_basic["numero_repeticiones"] or 1,
- limite_catadores=data_basic["numero_catadores"],
- instrucciones=data_basic["instrucciones"] or "Espere instrucciones del Presentador",
- )
-
- if not technique:
- raise ValueError("Error al guardar la técnica")
-
- # ////////////////////////////////////////////// #
- #
- # Second step: Create productos with their codes #
- #
- # ////////////////////////////////////////////// #
- codes = request.session["form_codes"]
-
- if not codes:
- raise ValueError("No hay códigos de productos")
-
- products_without_save = []
- for code in codes:
- product = Producto(
- codigoProducto=code,
- id_tecnica=technique
- )
- products_without_save.append(product)
-
- Producto.objects.bulk_create(products_without_save)
-
- # /////////////////////////////////////////////////////// #
- #
- # Third step: Create relations technique with Words Style #
- #
- # /////////////////////////////////////////////////////// #
- style_words = technique.id_estilo.nombre_estilo
-
- if style_words == "atributos":
- raw_ids_words = request.session["form_words"]
- ids_words = [int(id_w) for id_w in raw_ids_words]
-
- words = Palabra.objects.filter(id__in=ids_words)
-
- style_atribute = EsAtributo.objects.create(
- id_tecnica=technique
- )
-
- if not style_atribute:
- raise ValueError(
- "Error al intentar relacionar las palabras con la técnica")
-
- style_atribute.palabras.set(words)
-
- elif style_words == "vocabulario":
- name_vocabulary = request.session["form_words"]
- try:
- vocabulary = Vocabulario.objects.get(
- nombre_vocabulario=name_vocabulary)
- except Vocabulario.DoesNotExist:
- raise ValueError("Vocabulario no encontrado")
-
- es_vocabulary = EsVocabulario.objects.create(
- id_tecnica=technique,
- id_vocabulario=vocabulary
- )
- if not es_vocabulary:
- raise ValueError(
- "Error al intentar relacionar el vocabulario con la técnica")
-
- else:
- raise ValueError("Estilo de palabas no permitido")
-
- # //////////////////////////////////////////////////////// #
- #
- # Fourth step: Create session and relat with the technique #
- #
- # //////////////////////////////////////////////////////// #
- session = SesionSensorial.objects.create(
- nombre_sesion=data_basic["nombre_sesion"] if data_basic["nombre_sesion"] != "" else None,
- tecnica=technique,
- creadoPor=request.user.user_presentador
- )
-
- if not session:
- raise ValueError("Error al crear sesion sensorial")
-
- context = {
- "message": "sesión creada",
- "data": {
- "codigo_sesion": session.codigo_sesion,
- "nombre_sesion": session.nombre_sesion
- }
- }
-
- # ////////////////////////////////// #
- #
- # Final step: Delete date en session #
- #
- # ////////////////////////////////// #
- deleteDataSession(request)
- return JsonResponse(context)
-
- except ValueError as e:
- return general_error(f"Error: {e}")
- else:
- return general_error("No se ha establecido acción")
diff --git a/tecnicas/controllers/views_controller/create_session/panel_tags_controller.py b/tecnicas/controllers/views_controller/create_session/panel_tags_controller.py
index 852bd7d245c22894b486e2fd0f5a65e462a8b201..c41da248ea799117732f622804a8918f762de421 100644
--- a/tecnicas/controllers/views_controller/create_session/panel_tags_controller.py
+++ b/tecnicas/controllers/views_controller/create_session/panel_tags_controller.py
@@ -25,7 +25,7 @@ class PanelTagsController():
"form_new_tag": form_new_etiqueta
}
- return render(request, "tecnicas/create_sesion/configuracion-panel-tags.html", context_tags)
+ return render(request, "tecnicas/create_sesion/conf-panel-tags.html", context_tags)
@staticmethod
def controllPostEscalas(request: HttpRequest, data):
@@ -54,7 +54,7 @@ class PanelTagsController():
else:
context_tags["error"] = "ha ocurrido un error"
response = render(
- request, "tecnicas/create_sesion/configuracion-panel-tags.html", context_tags)
+ request, "tecnicas/create_sesion/conf-panel-tags.html", context_tags)
return response
diff --git a/tecnicas/controllers/views_controller/create_session/panel_words_controller.py b/tecnicas/controllers/views_controller/create_session/panel_words_controller.py
index 92d454c44096a43608ec5fbe936f3d873e99d5ff..d748614782dcac27a93bedb03f1ee355447adea6 100644
--- a/tecnicas/controllers/views_controller/create_session/panel_words_controller.py
+++ b/tecnicas/controllers/views_controller/create_session/panel_words_controller.py
@@ -7,7 +7,7 @@ import json
class PanelWordsController():
- current_url_escalas_atribute = "tecnicas/create_sesion/configuracion-panel-words.html"
+ current_url_escalas_atribute = "tecnicas/create_sesion/conf-panel-words.html"
current_url_escalas_vocabulary = "tecnicas/create_sesion/conf-panel-vocabulary.html"
def __init__(self):
diff --git a/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_cata_controller.py b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_cata_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..79a6329a92f3a51852a670792a14a9fa778ea687
--- /dev/null
+++ b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_cata_controller.py
@@ -0,0 +1,137 @@
+from .panel_create_controller import PanelCreateController
+from django.http import HttpRequest, JsonResponse
+from django.db import transaction
+from tecnicas.models import EsVocabulario, Tecnica, TipoTecnica, EstiloPalabra, EsAtributo, Vocabulario, Palabra, SesionSensorial, Producto
+from tecnicas.utils import deleteDataSession, general_error
+
+
+class PanelCreateCataController(PanelCreateController):
+ def __init__(self):
+ super().__init__()
+
+ @staticmethod
+ def controllPost(request: HttpRequest):
+ if request.POST.get('action') == 'create_session':
+ if not request.session.get("form_codes") or not request.session.get("form_words"):
+ deleteDataSession(request)
+ return general_error("No se ha especificado información necesaria para la creación de la sesión, por favor, vuelve a intentarlo")
+ try:
+ with transaction.atomic():
+ # //////////////////////////// #
+ #
+ # First step: Create technique #
+ #
+ # //////////////////////////// #
+ data_basic = request.session["form_basic"]
+ data_basic["numero_catadores"] = 0
+ data_basic["numero_repeticiones"] = 1
+
+ technique = Tecnica.objects.create(
+ tipo_tecnica=TipoTecnica.objects.get(
+ nombre_tecnica=data_basic["name_tecnica"]),
+ id_estilo=EstiloPalabra.objects.get(
+ nombre_estilo=data_basic["estilo_palabras"]),
+ repeticiones_max=data_basic["numero_repeticiones"] or 1,
+ limite_catadores=data_basic["numero_catadores"],
+ instrucciones=data_basic["instrucciones"] or "Espere instrucciones del Presentador",
+ )
+
+ if not technique:
+ raise ValueError("Error al guardar la técnica")
+
+ # ////////////////////////////////////////////// #
+ #
+ # Second step: Create productos with their codes #
+ #
+ # ////////////////////////////////////////////// #
+ codes = request.session["form_codes"]
+
+ if not codes:
+ raise ValueError("No hay códigos de productos")
+
+ products_without_save = []
+ for code in codes:
+ product = Producto(
+ codigoProducto=code,
+ id_tecnica=technique
+ )
+ products_without_save.append(product)
+
+ Producto.objects.bulk_create(products_without_save)
+
+ # /////////////////////////////////////////////////////// #
+ #
+ # Third step: Create relations technique with Words Style #
+ #
+ # /////////////////////////////////////////////////////// #
+ style_words = technique.id_estilo.nombre_estilo
+
+ if style_words == "atributos":
+ raw_ids_words = request.session["form_words"]
+ ids_words = [int(id_w) for id_w in raw_ids_words]
+
+ words = Palabra.objects.filter(id__in=ids_words)
+
+ style_atribute = EsAtributo.objects.create(
+ id_tecnica=technique
+ )
+
+ if not style_atribute:
+ raise ValueError(
+ "Error al intentar relacionar las palabras con la técnica")
+
+ style_atribute.palabras.set(words)
+
+ elif style_words == "vocabulario":
+ name_vocabulary = request.session["form_words"]
+ try:
+ vocabulary = Vocabulario.objects.get(
+ nombre_vocabulario=name_vocabulary)
+ except Vocabulario.DoesNotExist:
+ raise ValueError("Vocabulario no encontrado")
+
+ es_vocabulary = EsVocabulario.objects.create(
+ id_tecnica=technique,
+ id_vocabulario=vocabulary
+ )
+ if not es_vocabulary:
+ raise ValueError(
+ "Error al intentar relacionar el vocabulario con la técnica")
+
+ else:
+ raise ValueError("Estilo de palabas no permitido")
+
+ # //////////////////////////////////////////////////////// #
+ #
+ # Fourth step: Create session and relat with the technique #
+ #
+ # //////////////////////////////////////////////////////// #
+ session = SesionSensorial.objects.create(
+ nombre_sesion=data_basic["nombre_sesion"] if data_basic["nombre_sesion"] != "" else None,
+ tecnica=technique,
+ creadoPor=request.user.user_presentador
+ )
+
+ if not session:
+ raise ValueError("Error al crear sesion sensorial")
+
+ context = {
+ "message": "sesión creada",
+ "data": {
+ "codigo_sesion": session.codigo_sesion,
+ "nombre_sesion": session.nombre_sesion
+ }
+ }
+
+ # ////////////////////////////////// #
+ #
+ # Final step: Delete date en session #
+ #
+ # ////////////////////////////////// #
+ deleteDataSession(request)
+ return JsonResponse(context)
+
+ except ValueError as e:
+ return general_error(f"Error: {e}")
+ else:
+ return general_error("No se ha establecido acción")
diff --git a/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_controller.py b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea4a8559c91137c4b8a6c6e27f8f1f9293461869
--- /dev/null
+++ b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_controller.py
@@ -0,0 +1,17 @@
+from django.http import JsonResponse, HttpRequest
+from django.shortcuts import render
+
+class PanelCreateController():
+ url_template = 'tecnicas/create_sesion/creating_session.html'
+
+ def __init__(self):
+ pass
+
+ @staticmethod
+ def controllGet(request: HttpRequest):
+ return render(
+ request, PanelCreateController.url_template)
+
+ @staticmethod
+ def controllPost(request: HttpRequest):
+ return JsonResponse({"message": "Método no permitido"})
diff --git a/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_escalas_controller.py b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_escalas_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..727ebde507273373c4746ded1290bde26c3aed81
--- /dev/null
+++ b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_escalas_controller.py
@@ -0,0 +1,169 @@
+from .panel_create_controller import PanelCreateController
+from django.http import HttpRequest, JsonResponse
+from django.db import transaction
+from tecnicas.controllers import TecnicaController, EscalaController, ProductosController, OrdenesController, PalabrasController, EstiloPalabrasController, SesionController
+from tecnicas.models import EsVocabulario, Vocabulario
+from tecnicas.utils import deleteDataSession, general_error
+
+
+class PanelCreateEscalasController(PanelCreateController):
+ def __init__(self):
+ super().__init__()
+
+ @staticmethod
+ def controllPost(request: HttpRequest):
+ if request.POST.get('action') == 'create_session':
+ if not request.session.get("form_tags") or not request.session.get("form_codes") or not request.session.get("form_words"):
+ deleteDataSession(request)
+ return general_error("No se ha especificado información necesaria para la creación de la sesión, por favor, vuelve a intentarlo")
+ try:
+ with transaction.atomic():
+ # ////////////////////////////////////////////////////// #
+ #
+ # First step: Create technique and scale with their tags #
+ #
+ # ////////////////////////////////////////////////////// #
+ data_basic = request.session["form_basic"]
+ controllerTechnique = TecnicaController()
+ controllerTechnique.setTechniqueFromBasicData(
+ basic=data_basic)
+ technique = controllerTechnique.saveTechnique()
+ if not technique:
+ raise ValueError("Error al guardar la técnica")
+
+ data_scale = {
+ "id_scale": data_basic["tipo_escala"],
+ "size": data_basic["tamano_escala"],
+ "technique": technique
+ }
+
+ controllerScale = EscalaController(data=data_scale)
+
+ scale = controllerScale.saveScale()
+ if isinstance(scale, dict):
+ raise ValueError(scale["error"])
+
+ dict_tags = request.session["form_tags"]
+ saved_related_tags = controllerScale.realteTags(dict_tags)
+ if "error" in saved_related_tags:
+ raise ValueError(saved_related_tags["error"])
+
+ # ////////////////////////////////////////////////////////// #
+ #
+ # Second step: Create orders, productos and set the position #
+ #
+ # ////////////////////////////////////////////////////////// #
+ data_codes = request.session["form_codes"]
+
+ list_codes_dict = data_codes["product_codes"]
+
+ codes = []
+ for product in list_codes_dict:
+ code = next(iter(product.values()))
+ codes.append(code)
+
+ controllerProducts = ProductosController(
+ codes=codes,
+ technique=technique
+ )
+
+ controllerProducts.setProductsNoSave()
+ saved_prodcuts = controllerProducts.saveProducts()
+ if isinstance(saved_prodcuts, dict):
+ raise ValueError(saved_prodcuts["error"])
+
+ raw_sort_codes = data_codes["sort_codes"]
+ controllerOrdes = OrdenesController(
+ raw_orders=raw_sort_codes,
+ list_products=saved_prodcuts,
+ technique=technique
+ )
+
+ controllerOrdes.setOrdersToSave()
+ saved_orders = controllerOrdes.saveOrders()
+ if isinstance(saved_orders, dict):
+ raise ValueError(saved_orders["error"])
+
+ seded_positions = controllerOrdes.setPositions()
+ if isinstance(seded_positions, dict):
+ raise ValueError(seded_positions["error"])
+
+ saved_postions = controllerOrdes.savePositions()
+ if isinstance(saved_postions, dict):
+ raise ValueError(saved_prodcuts["error"])
+
+ # /////////////////////////////////////////////////////// #
+ #
+ # Third step: Create relations technique with Words Style #
+ #
+ # /////////////////////////////////////////////////////// #
+ style_words = technique.id_estilo.nombre_estilo
+ if style_words == "atributos":
+ ids_words = request.session["form_words"]
+ words_controller = PalabrasController(ids=ids_words)
+
+ words_to_use = words_controller.setWords()
+ if isinstance(words_to_use, dict):
+ raise ValueError(words_to_use["error"])
+
+ style_controller = EstiloPalabrasController(
+ technique=technique, words=words_to_use)
+
+ instace_style = style_controller.createAndSaveInstaceStyle()
+ if isinstance(instace_style, dict):
+ raise ValueError(instace_style["error"])
+
+ words_using = style_controller.relatedWords()
+ if isinstance(words_using, dict):
+ raise ValueError(words_using["error"])
+ elif style_words == "vocabulario":
+ name_vocabulary = request.session["form_words"]
+ vocabulary = Vocabulario.objects.get(
+ nombre_vocabulario=name_vocabulary)
+
+ es_vocabulary = EsVocabulario.objects.create(
+ id_tecnica=technique,
+ id_vocabulario=vocabulary
+ )
+ else:
+ raise ValueError("Estilo de palabas no permitido")
+
+ # //////////////////////////////////////////////////////// #
+ #
+ # Fourth step: Create session and relat with the technique #
+ #
+ # //////////////////////////////////////////////////////// #
+ session_controller = SesionController(
+ name_session=data_basic["nombre_sesion"] if data_basic["nombre_sesion"] != "" else None,
+ technique=technique,
+ creator=request.user.user_presentador
+ )
+
+ setting_session = session_controller.setSession()
+ if isinstance(setting_session, dict):
+ raise ValueError(setting_session["error"])
+
+ saved_session = session_controller.saveSession()
+ if isinstance(saved_session, dict):
+ raise ValueError(saved_session["error"])
+
+ context = {
+ "message": "sesión creada",
+ "data": {
+ "codigo_sesion": saved_session.codigo_sesion,
+ "nombre_sesion": saved_session.nombre_sesion
+ }
+ }
+
+ # ////////////////////////////////// #
+ #
+ # Final step: Delete date in session #
+ #
+ # ////////////////////////////////// #
+
+ deleteDataSession(request)
+ return JsonResponse(context)
+ except ValueError as e:
+ return general_error(f"Error: {e}")
+ else:
+ return general_error("No se ha establecido acción")
diff --git a/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_napping_controller.py b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_napping_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..6b145590018dd99950c447329cec13f7eb0dbe37
--- /dev/null
+++ b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_napping_controller.py
@@ -0,0 +1,114 @@
+from .panel_create_controller import PanelCreateController
+from django.http import HttpRequest, JsonResponse
+from tecnicas.models import Tecnica, TipoTecnica, EstiloPalabra, Producto, SesionSensorial, Modalidad, TecnicaModalidad
+from django.db import transaction
+from tecnicas.utils import deleteDataSession
+
+
+class PanelCreateNappingController(PanelCreateController):
+ def __init__(self):
+ super().__init__()
+
+ @staticmethod
+ def controllPost(request: HttpRequest):
+ if request.POST.get('action') == 'create_session':
+ if not request.session.get("form_basic") or not request.session.get("form_codes"):
+ deleteDataSession(request)
+ return general_error("No se ha especificado información necesaria para la creación de la sesión, por favor, vuelve a intentarlo")
+ try:
+ with transaction.atomic():
+ # //////////////////////////// #
+ #
+ # First step: Create technique #
+ #
+ # //////////////////////////// #
+ data_basic = request.session["form_basic"]
+ data_basic["numero_catadores"] = data_basic["numero_catadores"] or 2
+ data_basic["numero_repeticiones"] = 0
+
+ technique = Tecnica.objects.create(
+ tipo_tecnica=TipoTecnica.objects.get(
+ nombre_tecnica=data_basic["name_tecnica"]),
+ id_estilo=EstiloPalabra.objects.get(
+ nombre_estilo="napping"),
+ repeticiones_max=data_basic["numero_repeticiones"],
+ limite_catadores=data_basic["numero_catadores"],
+ instrucciones=data_basic["instrucciones"] or "Espere instrucciones del Presentador",
+ )
+
+ if not technique:
+ raise ValueError("Error al guardar la técnica")
+
+ # ////////////////////////////////////////////// #
+ #
+ # Second step: Create productos with their codes #
+ #
+ # ////////////////////////////////////////////// #
+ codes = request.session["form_codes"]
+
+ if not codes:
+ raise ValueError("No hay códigos de productos")
+
+ products_without_save = []
+ for code in codes:
+ product = Producto(
+ codigoProducto=code,
+ id_tecnica=technique
+ )
+ products_without_save.append(product)
+
+ Producto.objects.bulk_create(products_without_save)
+
+ # /////////////////////////////////////////////////////// #
+ #
+ # Third step: Create session and relat with the technique #
+ #
+ # /////////////////////////////////////////////////////// #
+ mod = Modalidad.objects.get(
+ nombre=data_basic["modalidad"])
+
+ if not mod:
+ raise ValueError("Modalidad no encontrada")
+
+ technique_mod = TecnicaModalidad.objects.create(
+ tecnica=technique,
+ modalidad=mod
+ )
+
+ if not technique_mod:
+ raise ValueError("Error al guardar la técnica")
+
+ # /////////////////////////////////////////////////////// #
+ #
+ # Fourth step: Create session and relat with the technique #
+ #
+ # /////////////////////////////////////////////////////// #
+ session = SesionSensorial.objects.create(
+ nombre_sesion=data_basic["nombre_sesion"] if data_basic["nombre_sesion"] != "" else None,
+ tecnica=technique,
+ creadoPor=request.user.user_presentador
+ )
+
+ if not session:
+ raise ValueError("Error al crear sesion sensorial")
+
+ context = {
+ "message": "sesión creada",
+ "data": {
+ "codigo_sesion": session.codigo_sesion,
+ "nombre_sesion": session.nombre_sesion
+ }
+ }
+
+ # ////////////////////////////////// #
+ #
+ # Final step: Delete date en session #
+ #
+ # ////////////////////////////////// #
+ deleteDataSession(request)
+ return JsonResponse(context)
+
+ except ValueError as e:
+ return general_error(f"Error: {e}")
+ else:
+ return general_error("No se ha establecido acción")
diff --git a/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_pf_controller.py b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_pf_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..b6e368c0f62f1919f6d5c082a2b852d482aa319d
--- /dev/null
+++ b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_pf_controller.py
@@ -0,0 +1,105 @@
+from .panel_create_controller import PanelCreateController
+from django.http import HttpRequest, JsonResponse
+from django.db import transaction
+from tecnicas.models import Tecnica, TipoTecnica, EstiloPalabra, SesionSensorial, Escala, TipoEscala, Producto
+from tecnicas.utils import deleteDataSession, general_error
+
+
+class PanelCreatePFController(PanelCreateController):
+ def __init__(self):
+ super().__init__()
+
+ @staticmethod
+ def controllPost(request: HttpRequest):
+ if request.POST.get('action') == 'create_session':
+ if not request.session.get("form_codes"):
+ deleteDataSession(request)
+ return general_error("No se ha especificado información necesaria para la creación de la sesión, por favor, vuelve a intentarlo")
+ try:
+ with transaction.atomic():
+ # ////////////////////////////////////// #
+ #
+ # First step: Create technique and scale #
+ #
+ # ////////////////////////////////////// #
+ data_basic = request.session["form_basic"]
+ phases_before_reptition = 2
+
+ technique = Tecnica.objects.create(
+ tipo_tecnica=TipoTecnica.objects.get(
+ nombre_tecnica=data_basic["name_tecnica"]),
+ id_estilo=EstiloPalabra.objects.get(
+ nombre_estilo="perfil flash"),
+ repeticiones_max=data_basic["numero_repeticiones"] +
+ phases_before_reptition,
+ limite_catadores=data_basic["numero_catadores"],
+ instrucciones=data_basic["instrucciones"] or "Espere instrucciones del Analista",
+ )
+
+ if not technique:
+ raise ValueError("Error al guardar la técnica")
+
+ created_scale = Escala.objects.create(
+ id_tipo_escala=TipoEscala.objects.get(
+ nombre_escala="estructurada"),
+ longitud=data_basic["numero_productos"],
+ tecnica=technique
+ )
+
+ if not created_scale:
+ raise ValueError("No se ha podido crear la escala")
+
+ # ////////////////////////////////////////////// #
+ #
+ # Second step: Create productos with their codes #
+ #
+ # ////////////////////////////////////////////// #
+ codes = request.session["form_codes"]
+
+ if not codes:
+ raise ValueError("No hay códigos de productos")
+
+ products_without_save = []
+ for code in codes:
+ product = Producto(
+ codigoProducto=code,
+ id_tecnica=technique
+ )
+ products_without_save.append(product)
+
+ Producto.objects.bulk_create(products_without_save)
+
+ # /////////////////////////////////////////////////////// #
+ #
+ # Third step: Create session and relat with the technique #
+ #
+ # /////////////////////////////////////////////////////// #
+ session = SesionSensorial.objects.create(
+ nombre_sesion=data_basic["nombre_sesion"] if data_basic["nombre_sesion"] != "" else None,
+ tecnica=technique,
+ creadoPor=request.user.user_presentador
+ )
+
+ if not session:
+ raise ValueError("Error al crear sesion sensorial")
+
+ context = {
+ "message": "sesión creada",
+ "data": {
+ "codigo_sesion": session.codigo_sesion,
+ "nombre_sesion": session.nombre_sesion
+ }
+ }
+
+ # ////////////////////////////////// #
+ #
+ # Final step: Delete date en session #
+ #
+ # ////////////////////////////////// #
+ deleteDataSession(request)
+ return JsonResponse(context)
+
+ except ValueError as e:
+ return general_error(f"Error: {e}")
+ else:
+ return general_error("No se ha establecido acción")
diff --git a/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_rata_controller.py b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_rata_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..eee8462ae591dc099c787e9d964230173cb98209
--- /dev/null
+++ b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_rata_controller.py
@@ -0,0 +1,155 @@
+from .panel_create_controller import PanelCreateController
+from django.http import HttpRequest, JsonResponse
+from django.db import transaction
+from tecnicas.controllers import EscalaController
+from tecnicas.models import EsVocabulario, Tecnica, TipoTecnica, EstiloPalabra, EsAtributo, Vocabulario, Palabra, SesionSensorial, Producto
+from tecnicas.utils import deleteDataSession, general_error
+
+
+class PanelCreateRataController(PanelCreateController):
+ def __init__(self):
+ super().__init__()
+
+ @staticmethod
+ def controllPost(request: HttpRequest):
+ if request.POST.get('action') == 'create_session':
+ if not request.session.get("form_tags") or not request.session.get("form_codes") or not request.session.get("form_words"):
+ deleteDataSession(request)
+ return general_error("No se ha especificado información necesaria para la creación de la sesión, por favor, vuelve a intentarlo")
+ try:
+ with transaction.atomic():
+ # ////////////////////////////////////////////////////// #
+ #
+ # First step: Create technique and scale with their tags #
+ #
+ # ////////////////////////////////////////////////////// #
+ data_basic = request.session["form_basic"]
+ data_basic["numero_catadores"] = 0
+ data_basic["numero_repeticiones"] = 1
+
+ technique = Tecnica.objects.create(
+ tipo_tecnica=TipoTecnica.objects.get(
+ nombre_tecnica=data_basic["name_tecnica"]),
+ id_estilo=EstiloPalabra.objects.get(
+ nombre_estilo=data_basic["estilo_palabras"]),
+ repeticiones_max=data_basic["numero_repeticiones"] or 1,
+ limite_catadores=data_basic["numero_catadores"],
+ instrucciones=data_basic["instrucciones"] or "Espere instrucciones del Presentador",
+ )
+
+ if not technique:
+ raise ValueError("Error al guardar la técnica")
+
+ data_scale = {
+ "id_scale": data_basic["tipo_escala"],
+ "size": data_basic["tamano_escala"],
+ "technique": technique
+ }
+
+ controllerScale = EscalaController(data=data_scale)
+
+ scale = controllerScale.saveScale()
+ if isinstance(scale, dict):
+ raise ValueError(scale["error"])
+
+ dict_tags = request.session["form_tags"]
+ saved_related_tags = controllerScale.realteTags(dict_tags)
+ if "error" in saved_related_tags:
+ raise ValueError(saved_related_tags["error"])
+
+ # ////////////////////////////////////////////// #
+ #
+ # Second step: Create productos with their codes #
+ #
+ # ////////////////////////////////////////////// #
+ codes = request.session["form_codes"]
+
+ if not codes:
+ raise ValueError("No hay códigos de productos")
+
+ products_without_save = []
+ for code in codes:
+ product = Producto(
+ codigoProducto=code,
+ id_tecnica=technique
+ )
+ products_without_save.append(product)
+
+ Producto.objects.bulk_create(products_without_save)
+
+ # /////////////////////////////////////////////////////// #
+ #
+ # Third step: Create relations technique with Words Style #
+ #
+ # /////////////////////////////////////////////////////// #
+ style_words = technique.id_estilo.nombre_estilo
+
+ if style_words == "atributos":
+ raw_ids_words = request.session["form_words"]
+ ids_words = [int(id_w) for id_w in raw_ids_words]
+
+ words = Palabra.objects.filter(id__in=ids_words)
+
+ style_atribute = EsAtributo.objects.create(
+ id_tecnica=technique
+ )
+
+ if not style_atribute:
+ raise ValueError(
+ "Error al intentar relacionar las palabras con la técnica")
+
+ style_atribute.palabras.set(words)
+
+ elif style_words == "vocabulario":
+ name_vocabulary = request.session["form_words"]
+ try:
+ vocabulary = Vocabulario.objects.get(
+ nombre_vocabulario=name_vocabulary)
+ except Vocabulario.DoesNotExist:
+ raise ValueError("Vocabulario no encontrado")
+
+ es_vocabulary = EsVocabulario.objects.create(
+ id_tecnica=technique,
+ id_vocabulario=vocabulary
+ )
+ if not es_vocabulary:
+ raise ValueError(
+ "Error al intentar relacionar el vocabulario con la técnica")
+
+ else:
+ raise ValueError("Estilo de palabas no permitido")
+
+ # //////////////////////////////////////////////////////// #
+ #
+ # Fourth step: Create session and relat with the technique #
+ #
+ # //////////////////////////////////////////////////////// #
+ session = SesionSensorial.objects.create(
+ nombre_sesion=data_basic["nombre_sesion"] if data_basic["nombre_sesion"] != "" else "",
+ tecnica=technique,
+ creadoPor=request.user.user_presentador
+ )
+
+ if not session:
+ raise ValueError("Error al crear sesion sensorial")
+
+ context = {
+ "message": "sesión creada",
+ "data": {
+ "codigo_sesion": session.codigo_sesion,
+ "nombre_sesion": session.nombre_sesion
+ }
+ }
+
+ # ////////////////////////////////// #
+ #
+ # Final step: Delete date en session #
+ #
+ # ////////////////////////////////// #
+
+ deleteDataSession(request)
+ return JsonResponse(context)
+ except ValueError as e:
+ return general_error(f"Error: {e}")
+ else:
+ return general_error("No se ha establecido acción")
diff --git a/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_sort_controller.py b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_sort_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..4d98ce32fe5948f299738bf230be6783c1d13080
--- /dev/null
+++ b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_sort_controller.py
@@ -0,0 +1,95 @@
+from .panel_create_controller import PanelCreateController
+from django.http import HttpRequest, JsonResponse
+from django.db import transaction
+from tecnicas.models import Tecnica, TipoTecnica, EstiloPalabra, SesionSensorial, Producto
+from tecnicas.utils import deleteDataSession, general_error
+
+
+class PanelCreateSortController(PanelCreateController):
+ def __init__(self):
+ super().__init__()
+
+ @staticmethod
+ def controllPost(request: HttpRequest):
+ if request.POST.get('action') == 'create_session':
+ if not request.session.get("form_basic") or not request.session.get("form_codes"):
+ deleteDataSession(request)
+ return general_error("No se ha especificado información necesaria para la creación de la sesión, por favor, vuelve a intentarlo")
+ try:
+ with transaction.atomic():
+ # //////////////////////////// #
+ #
+ # First step: Create technique #
+ #
+ # //////////////////////////// #
+ data_basic = request.session["form_basic"]
+ data_basic["numero_catadores"] = data_basic["numero_catadores"] or 1
+ data_basic["numero_repeticiones"] = 1
+
+ technique = Tecnica.objects.create(
+ tipo_tecnica=TipoTecnica.objects.get(
+ nombre_tecnica=data_basic["name_tecnica"]),
+ id_estilo=EstiloPalabra.objects.get(
+ nombre_estilo="sort"),
+ repeticiones_max=data_basic["numero_repeticiones"],
+ limite_catadores=data_basic["numero_catadores"],
+ instrucciones=data_basic["instrucciones"] or "Espere instrucciones del Presentador",
+ )
+
+ if not technique:
+ raise ValueError("Error al guardar la técnica")
+
+ # ////////////////////////////////////////////// #
+ #
+ # Second step: Create productos with their codes #
+ #
+ # ////////////////////////////////////////////// #
+ codes = request.session["form_codes"]
+
+ if not codes:
+ raise ValueError("No hay códigos de productos")
+
+ products_without_save = []
+ for code in codes:
+ product = Producto(
+ codigoProducto=code,
+ id_tecnica=technique
+ )
+ products_without_save.append(product)
+
+ Producto.objects.bulk_create(products_without_save)
+
+ # /////////////////////////////////////////////////////// #
+ #
+ # Third step: Create session and relat with the technique #
+ #
+ # /////////////////////////////////////////////////////// #
+ session = SesionSensorial.objects.create(
+ nombre_sesion=data_basic["nombre_sesion"] if data_basic["nombre_sesion"] != "" else None,
+ tecnica=technique,
+ creadoPor=request.user.user_presentador
+ )
+
+ if not session:
+ raise ValueError("Error al crear sesion sensorial")
+
+ context = {
+ "message": "sesión creada",
+ "data": {
+ "codigo_sesion": session.codigo_sesion,
+ "nombre_sesion": session.nombre_sesion
+ }
+ }
+
+ # ////////////////////////////////// #
+ #
+ # Final step: Delete date en session #
+ #
+ # ////////////////////////////////// #
+ deleteDataSession(request)
+ return JsonResponse(context)
+
+ except ValueError as e:
+ return general_error(f"Error: {e}")
+ else:
+ return general_error("No se ha establecido acción")
diff --git a/tecnicas/controllers/views_controller/session_management/details/details_cata_controller.py b/tecnicas/controllers/views_controller/session_management/details/details_cata_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b21cde747f701a2ed7ab95a6737339954c638a2
--- /dev/null
+++ b/tecnicas/controllers/views_controller/session_management/details/details_cata_controller.py
@@ -0,0 +1,72 @@
+from django.db.models import F
+from tecnicas.models import SesionSensorial, Calificacion, ValorBooleano
+from tecnicas.controllers import PalabrasController
+from tecnicas.utils import defaultdict_to_dict
+from .details_controller import DetallesController
+from collections import defaultdict
+
+
+class DetallesCATAController(DetallesController):
+ def __init__(self, session: SesionSensorial):
+ super().__init__(session)
+ self.url_template = "tecnicas/manage_sesions/details-session-cata.html"
+ self.url_next = "cata_system:monitor_sesion"
+
+ def getContext(self):
+ technique = self.session.tecnica
+
+ self.context = {
+ "sesion": self.session,
+ "use_technique": technique
+ }
+
+ # Recuperar palabras
+ self.words = PalabrasController.getWordsInTechnique(
+ self.session.tecnica)
+ self.context["palabras"] = [word.nombre_palabra for word in self.words]
+
+ # Intentar recuperar las calificaciones
+ ratings_for_repetition = []
+
+ ratings = list(Calificacion.objects.filter(
+ id_tecnica=technique))
+
+ if not ratings:
+ self.context["calificaciones"] = ratings_for_repetition
+ self.context["existen_calificaciones"] = False
+ return self.context
+
+ data = (
+ ValorBooleano.objects
+ .filter(id_dato__id_calificacion__in=ratings)
+ .values(
+ nombre_palabra=F("id_dato__id_palabra__nombre_palabra"),
+ repeticion=F("id_dato__id_calificacion__num_repeticion"),
+ producto_code=F(
+ "id_dato__id_calificacion__id_producto__codigoProducto"),
+ usuario_catador=F(
+ "id_dato__id_calificacion__id_catador__user__username"),
+ dato_valor=F("valor")
+ )
+ )
+
+ ratings_for_repetition = defaultdict(
+ lambda: defaultdict(lambda: defaultdict(list)))
+
+ for item in data:
+ user = item["usuario_catador"]
+ rep = item["repeticion"]
+ prod = item["producto_code"]
+
+ ratings_for_repetition[rep][user][prod].append({
+ "nombre_palabra": item["nombre_palabra"],
+ "dato_valor": item["dato_valor"]
+ })
+
+ self.context["calificaciones"] = defaultdict_to_dict(
+ ratings_for_repetition)
+ self.context["existen_calificaciones"] = True
+
+ # Se comprueba que ya no se pueda iniciar la repeticion
+ self.context["fin_repeticiones"] = technique.repeticion >= technique.repeticiones_max
+ return self.context
diff --git a/tecnicas/controllers/views_controller/session_management/details/details_controller.py b/tecnicas/controllers/views_controller/session_management/details/details_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..ae6b3a793672804000c0c712225cf929299b4005
--- /dev/null
+++ b/tecnicas/controllers/views_controller/session_management/details/details_controller.py
@@ -0,0 +1,70 @@
+from django.http import HttpRequest
+from django.shortcuts import render, redirect
+from django.urls import reverse
+from tecnicas.models import SesionSensorial, Presentador, Tecnica, Participacion
+from tecnicas.controllers import ParticipacionController
+
+
+class DetallesController():
+ url_template: str
+ url_next = "cata_system:monitor_sesion"
+
+ def __init__(self, session: SesionSensorial):
+ self.session = session
+
+ def controllGetResponse(self, request: HttpRequest, error: str = "", message: str = ""):
+ context = self.getContext()
+
+ if error != "" or error:
+ context["error"] = error
+ if message != "" or message:
+ context["message"] = message
+
+ return render(
+ request, self.url_template, context)
+
+ def getContext(self):
+ return {}
+
+ def deleteSesorialSession(self):
+ technique = Tecnica.objects.get(id=self.session.tecnica.id)
+ technique.delete()
+
+ def startRepetition(self, presenter: Presentador, request: HttpRequest):
+ creator = presenter
+ technique = self.session.tecnica
+
+ if creator.user.username != self.session.creadoPor.user.username:
+ return self.controllGetResponse(error="Solo el presentador que crea la sesión puede iniciar la repetición", request=request)
+ elif self.session.activo:
+ return self.controllGetResponse(error="La sesión ya está activada", request=request)
+ elif technique.repeticion >= technique.repeticiones_max:
+ return self.controllGetResponse(error="Se ha alcanzado el número de repeticiones máxima", request=request)
+
+ is_update_participations = self.setParticipationsToNoFinished()
+ if not is_update_participations:
+ return self.controllGetResponse(error="Error al actualizar las participaciones", request=request)
+
+ self.session.activo = True
+ technique.repeticion = technique.repeticion + 1
+
+ technique.save()
+ self.session.save()
+
+ parameters = {
+ "session_code": self.session.codigo_sesion
+ }
+ return redirect(
+ reverse(self.url_next, kwargs=parameters))
+
+ def setParticipationsToNoFinished(self):
+ there_participacions = Participacion.objects.filter(
+ tecnica=self.session.tecnica).exists()
+
+ if there_participacions:
+ (is_update_participations,
+ message) = ParticipacionController.outAllInSession(self.session)
+
+ return is_update_participations
+
+ return True
\ No newline at end of file
diff --git a/tecnicas/controllers/views_controller/session_management/details_escala_controller.py b/tecnicas/controllers/views_controller/session_management/details/details_escala_controller.py
similarity index 52%
rename from tecnicas/controllers/views_controller/session_management/details_escala_controller.py
rename to tecnicas/controllers/views_controller/session_management/details/details_escala_controller.py
index a36c8080fa544fee777efeaccb516e4e0b49ea4e..f5838b4c1228c7d4cdfdc732eb0b0351c99168c7 100644
--- a/tecnicas/controllers/views_controller/session_management/details_escala_controller.py
+++ b/tecnicas/controllers/views_controller/session_management/details/details_escala_controller.py
@@ -11,40 +11,26 @@ Encabezados de como deben de aparecer los datos juntos
| Repeticion | Codigo Producto | Catador | P1 | P2 | P3 | Pn |
'''
-from django.http import HttpRequest
-from django.shortcuts import render, redirect
-from django.urls import reverse
-from tecnicas.models import SesionSensorial, Presentador, Participacion, Calificacion, Escala
-from tecnicas.controllers import DatoController, PalabrasController, ParticipacionController
+from tecnicas.models import SesionSensorial, Calificacion, Escala
+from tecnicas.controllers import DatoController, PalabrasController
from .details_controller import DetallesController
-from tecnicas.utils import defaultdict_to_dict, controller_error
+from tecnicas.utils import defaultdict_to_dict
from collections import defaultdict
class DetallesEscalasController(DetallesController):
- url_template = "tecnicas/manage_sesions/detalles-sesion.html"
-
def __init__(self, session: SesionSensorial):
super().__init__(session)
-
- def getResponse(self, request: HttpRequest, error: str = "", message: str = ""):
- context = self.getContext()
-
- if error != "" or error:
- context["error"] = error
- if message != "" or message:
- context["message"] = message
-
- return render(
- request, self.url_template, context)
+ self.url_template = "tecnicas/manage_sesions/details-session.html"
+ self.url_next = "cata_system:monitor_sesion"
def getContext(self):
+ technique = self.session.tecnica
+
self.context = {
- "use_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
+ "sesion": self.session,
+ "use_technique": technique.tipo_tecnica.nombre_tecnica
}
- self.context["sesion"] = self.session
-
- technique = self.session.tecnica
# Datos de la escala usada
scale: Escala = technique.escala_tecnica
@@ -94,35 +80,3 @@ class DetallesEscalasController(DetallesController):
self.context["fin_repeticiones"] = technique.repeticion >= technique.repeticiones_max
return self.context
-
- def startRepetition(self, presenter: Presentador, request: HttpRequest):
- creator = presenter
- technique = self.session.tecnica
-
- if creator.user.username != self.session.creadoPor.user.username:
- return self.getResponse(error="Solo el presentador que crea la sesión puede iniciar la repetición", request=request)
- elif self.session.activo:
- return self.getResponse(error="La sesión ya está activada", request=request)
- elif technique.repeticion >= technique.repeticiones_max:
- return self.getResponse(error="Se ha alcanzado el número de repeticiones máxima", request=request)
-
- there_participacions = Participacion.objects.filter(
- tecnica=technique).exists()
-
- if there_participacions:
- (is_update_participations,
- message) = ParticipacionController.outAllInSession(self.session)
- if not is_update_participations:
- return self.getResponse(error=message, request=request)
-
- self.session.activo = True
- technique.repeticion = technique.repeticion + 1
-
- technique.save()
- self.session.save()
-
- parameters = {
- "session_code": self.session.codigo_sesion
- }
- return redirect(
- reverse("cata_system:monitor_sesion", kwargs=parameters))
diff --git a/tecnicas/controllers/views_controller/session_management/details/details_napping_controller.py b/tecnicas/controllers/views_controller/session_management/details/details_napping_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..f1f213fa5b1cb29b548c2d2cc169eb5f720add6d
--- /dev/null
+++ b/tecnicas/controllers/views_controller/session_management/details/details_napping_controller.py
@@ -0,0 +1,508 @@
+from django.http import HttpRequest
+from django.shortcuts import redirect
+from django.urls import reverse
+from django.db.models import F
+from .details_controller import DetallesController
+from tecnicas.models import (
+ SesionSensorial, Presentador, Modalidad, TecnicaModalidad, Catador,
+ Participacion, DatoPunto, Calificacion, GrupoProducto, ValorBooleano,
+ ValorDecimal, Producto, Escala, EsVocabulario, Vocabulario
+)
+from tecnicas.utils import defaultdict_to_dict
+from collections import defaultdict
+
+
+class DetallesNappingController(DetallesController):
+ def __init__(self, session: SesionSensorial):
+ super().__init__(session)
+ self.url_template = "tecnicas/manage_sesions/details-session-napping.html"
+ self.url_next = "cata_system:monitor_sesion"
+ self.context = {}
+
+ def getContext(self):
+ self.context["session"] = self.session
+
+ self.defineStatus()
+ self.setIsEndSession()
+ self.setDataTable()
+
+ return self.context
+
+ def defineStatus(self):
+ repetition = self.session.tecnica.repeticion
+ mod = TecnicaModalidad.objects.get(
+ tecnica=self.session.tecnica)
+
+ self.context["mod_tech"] = mod.modalidad.nombre
+ self.context["mode"] = mod.modalidad.nombre
+ if mod.modalidad.nombre == "sin modalidad":
+ self.context["mod_tech"] = "No se usa modalidad"
+
+ if not self.session.activo:
+ self.context["status"] = "Listo para iniciar la sesión con Napping"
+ elif self.session.activo:
+ self.context["status"] = "Sesión con en curso"
+
+ def controllPostResponse(self, request: HttpRequest, action: str):
+ if action == "start_sin_modalidad":
+ response = self.startNapping(request=request)
+
+ elif action == "start_perfil_ultra_flash":
+ response = self.startNapping(request=request)
+
+ elif action == "start_sorting":
+ response = self.startNapping(request=request)
+
+ elif action == "combine_sessions":
+ response = self.combineSessions(request=request)
+
+ elif action == "delete_session":
+ self.deleteSesorialSession()
+ response = redirect(
+ reverse("cata_system:panel_sesiones", kwargs={"page": 1}))
+
+ else:
+ response = self.controllGetResponse(
+ error="Modalidad sin implantar", request=request)
+
+ return response
+
+ def startNapping(self, request: HttpRequest):
+ if request.user.user_presentador.user.username != self.session.creadoPor.user.username:
+ return self.controllGetResponse(error="Solo el presentador que crea la sesión puede iniciar la repetición", request=request)
+ elif self.session.activo:
+ return self.controllGetResponse(error="La sesión ya está activada", request=request)
+
+ is_update_participations = self.setParticipationsToNoFinished()
+ if not is_update_participations:
+ return self.controllGetResponse(error="Error al actualizar las participaciones", request=request)
+
+ self.session.activo = True
+ self.session.save()
+
+ parameters = {
+ "session_code": self.session.codigo_sesion
+ }
+ return redirect(
+ reverse(self.url_next, kwargs=parameters))
+
+ def setDataTable(self):
+ participations = Participacion.objects.filter(
+ tecnica=self.session.tecnica).select_related("catador")
+ testers = [participation.catador for participation in participations]
+ self.context["testers"] = testers
+
+ ratings = Calificacion.objects.filter(id_tecnica=self.session.tecnica)
+
+ coordinates = (
+ DatoPunto.objects.filter(calificacion__in=ratings)
+ .values(
+ producto=F("calificacion__id_producto__codigoProducto"),
+ catador=F("calificacion__id_catador__user__username"),
+ px=F("x"),
+ py=F("y"),
+ ))
+
+ if not coordinates.exists():
+ self.context["there_data"] = False
+ return []
+
+ coordinates_by_product = defaultdict(dict)
+
+ for coordinate in coordinates:
+ coordinates_by_product[coordinate["producto"]][coordinate["catador"]] = {
+ "px": coordinate["px"],
+ "py": coordinate["py"],
+ }
+
+ self.context["coordinates_no_mode"] = defaultdict_to_dict(
+ coordinates_by_product)
+
+ # Add word frequency data for perfil ultra flash mode
+ mod = TecnicaModalidad.objects.get(tecnica=self.session.tecnica)
+ if mod.modalidad.nombre == "perfil ultra flash":
+ self.setWordFrequencies(ratings)
+ elif mod.modalidad.nombre == "sorting":
+ self.setSortingData()
+
+ self.context["there_data"] = True
+
+ def setWordFrequencies(self, ratings):
+ from collections import Counter
+
+ # Prefetch palabras to optimize queries
+ ratings_with_words = ratings.prefetch_related(
+ 'palabras').select_related('id_producto')
+
+ # Dictionary to store word frequencies by product
+ word_frequencies_by_product = defaultdict(Counter)
+ all_words_set = set()
+
+ for rating in ratings_with_words:
+ producto_code = rating.id_producto.codigoProducto
+ words = rating.palabras.all()
+
+ for word in words:
+ word_name = word.nombre_palabra
+ word_frequencies_by_product[producto_code][word_name] += 1
+ all_words_set.add(word_name)
+
+ # Convert Counter objects to regular dicts and sort words alphabetically
+ word_frequencies_dict = {
+ product: dict(frequencies)
+ for product, frequencies in word_frequencies_by_product.items()
+ }
+
+ # Sort all words alphabetically for consistent column ordering
+ all_words_sorted = sorted(all_words_set)
+
+ self.context["word_frequencies"] = word_frequencies_dict
+ self.context["all_words"] = all_words_sorted
+
+ def setSortingData(self):
+ # Get all ratings for this technique to access DatoPunto
+ ratings = Calificacion.objects.filter(id_tecnica=self.session.tecnica)
+
+ # Get coordinates for all products
+ coordinates = (
+ DatoPunto.objects.filter(calificacion__in=ratings)
+ .values(
+ producto=F("calificacion__id_producto__codigoProducto"),
+ producto_id=F("calificacion__id_producto__id"),
+ catador=F("calificacion__id_catador__user__username"),
+ catador_id=F("calificacion__id_catador__id"),
+ px=F("x"),
+ py=F("y"),
+ ))
+
+ # Create a mapping of (catador_id, producto_id) -> coordinates
+ coord_map = {}
+ for coord in coordinates:
+ key = (coord["catador_id"], coord["producto_id"])
+ coord_map[key] = {
+ "px": coord["px"],
+ "py": coord["py"],
+ "producto": coord["producto"],
+ "catador": coord["catador"]
+ }
+
+ # Get all groups with their products and words
+ grupos = (
+ GrupoProducto.objects.filter(tecnica=self.session.tecnica)
+ .prefetch_related("productos", "palabras")
+ .select_related("catador__user")
+ )
+
+ # Create a mapping of (catador_id, producto_id) -> words
+ words_map = defaultdict(list)
+ for grupo in grupos:
+ catador_id = grupo.catador.id
+ words = [palabra.nombre_palabra for palabra in grupo.palabras.all()]
+ words_str = ";".join(words) if words else ""
+
+ for producto in grupo.productos.all():
+ key = (catador_id, producto.id)
+ words_map[key] = words_str
+
+ # Structure final data: product -> catador -> {px, py, words}
+ sorting_data = defaultdict(dict)
+
+ for key, coord_data in coord_map.items():
+ catador_id, producto_id = key
+ producto_code = coord_data["producto"]
+ catador_username = coord_data["catador"]
+
+ sorting_data[producto_code][catador_username] = {
+ "px": coord_data["px"],
+ "py": coord_data["py"],
+ "words": words_map.get(key, "")
+ }
+
+ self.context["sorting_data"] = defaultdict_to_dict(sorting_data)
+
+ def setIsEndSession(self):
+ if not self.session.activo and self.session.tecnica.repeticion < 1:
+ self.context["finished"] = False
+ return
+ elif self.session.activo:
+ self.context["finished"] = False
+ return
+ elif not self.session.activo and self.session.tecnica.repeticion >= 1:
+ self.context["finished"] = True
+ return
+
+ # ==================== SESSION COMBINATION METHODS ====================
+
+ def combineSessions(self, request: HttpRequest):
+ """Handle session combination request"""
+ session_b_code = request.POST.get("session_b_code", "").strip()
+
+ if not session_b_code:
+ return self.controllGetResponse(
+ error="Debe proporcionar un código de sesión", request=request)
+
+ # Validate and get session B
+ validation_result = self.validateSessionCombination(session_b_code)
+
+ if validation_result.get("error"):
+ return self.controllGetResponse(
+ error=validation_result["error"], request=request)
+
+ session_b = validation_result["session_b"]
+ technique_type = validation_result["technique_type"]
+
+ # Get combined data based on technique type
+ if technique_type == "cata":
+ combined_data = self.getCombinedDataForCATA(session_b)
+ elif technique_type == "rata":
+ combined_data = self.getCombinedDataForRATA(session_b)
+ elif technique_type == "escalas":
+ combined_data = self.getCombinedDataForEscalas(session_b)
+ else:
+ return self.controllGetResponse(
+ error="Tipo de técnica no soportado para combinación", request=request)
+
+ # Add combined data to context
+ self.context["combined_data"] = combined_data
+ self.context["session_b"] = session_b
+ self.context["session_b_technique_type"] = technique_type
+
+ return self.controllGetResponse(request=request)
+
+ def validateSessionCombination(self, session_b_code: str):
+ """Validate that Session B can be combined with Session A (Napping)"""
+ result = {"error": None, "session_b": None, "technique_type": None}
+
+ # Check if Session B exists
+ try:
+ session_b = SesionSensorial.objects.select_related(
+ "tecnica__tipo_tecnica").get(codigo_sesion=session_b_code)
+ except SesionSensorial.DoesNotExist:
+ result["error"] = f"No existe una sesión con el código: {session_b_code}"
+ return result
+
+ # Check if Session B technique is CATA, RATA, or Escalas
+ technique_type = session_b.tecnica.tipo_tecnica.nombre_tecnica
+ valid_techniques = ["cata", "rata", "escalas"]
+
+ if technique_type not in valid_techniques:
+ result[
+ "error"] = f"La sesión B debe usar CATA, RATA o Escalas. Técnica actual: {technique_type}"
+ return result
+
+ # Check if Session B is finished
+ if session_b.activo:
+ result["error"] = "La sesión B debe estar finalizada (no activa)"
+ return result
+
+ if session_b.tecnica.repeticion < 1:
+ result["error"] = "La sesión B debe haber completado al menos una repetición"
+ return result
+
+ # Get products from both sessions
+ products_a = set(
+ Producto.objects.filter(
+ calificacion_producto__id_tecnica=self.session.tecnica
+ ).values_list("codigoProducto", flat=True).distinct()
+ )
+
+ products_b = set(
+ Producto.objects.filter(
+ calificacion_producto__id_tecnica=session_b.tecnica
+ ).values_list("codigoProducto", flat=True).distinct()
+ )
+
+ # Check if products match
+ if products_a != products_b:
+ result["error"] = f"Los productos no coinciden. Sesión A: {len(products_a)} productos, Sesión B: {len(products_b)} productos"
+ return result
+
+ # Get tasters from both sessions
+ tasters_a = set(
+ Participacion.objects.filter(
+ tecnica=self.session.tecnica
+ ).values_list("catador__user__username", flat=True)
+ )
+
+ tasters_b = set(
+ Participacion.objects.filter(
+ tecnica=session_b.tecnica
+ ).values_list("catador__user__username", flat=True)
+ )
+
+ # Check if tasters match
+ if tasters_a != tasters_b:
+ result["error"] = f"Los catadores no coinciden. Sesión A: {len(tasters_a)} catadores, Sesión B: {len(tasters_b)} catadores"
+ return result
+
+ result["session_b"] = session_b
+ result["technique_type"] = technique_type
+ return result
+
+ def getCombinedDataForCATA(self, session_b: SesionSensorial):
+ """Get combined data for CATA technique (word frequencies)"""
+ from collections import Counter
+
+ # Get all ratings for session B
+ ratings_b = Calificacion.objects.filter(id_tecnica=session_b.tecnica)
+
+ # Get boolean values (CATA uses boolean)
+ data = (
+ ValorBooleano.objects
+ .filter(id_dato__id_calificacion__in=ratings_b, valor=True)
+ .values(
+ palabra=F("id_dato__id_palabra__nombre_palabra"),
+ producto=F(
+ "id_dato__id_calificacion__id_producto__codigoProducto"),
+ )
+ )
+
+ # Count word frequencies per product
+ word_frequencies = defaultdict(Counter)
+ all_words_set = set()
+
+ for item in data:
+ palabra = item["palabra"]
+ producto = item["producto"]
+ word_frequencies[producto][palabra] += 1
+ all_words_set.add(palabra)
+
+ # Get vocabulary info if exists
+ vocabulary_info = self.getVocabularyInfo(session_b.tecnica)
+
+ return {
+ "word_frequencies": defaultdict_to_dict(word_frequencies),
+ "all_words": sorted(all_words_set),
+ "vocabulary_info": vocabulary_info,
+ }
+
+ def getCombinedDataForRATA(self, session_b: SesionSensorial):
+ """Get combined data for RATA technique (word averages)"""
+
+ # Get all ratings for session B
+ ratings_b = Calificacion.objects.filter(id_tecnica=session_b.tecnica)
+
+ # Get decimal values (RATA uses decimal)
+ data = (
+ ValorDecimal.objects
+ .filter(id_dato__id_calificacion__in=ratings_b)
+ .values(
+ palabra=F("id_dato__id_palabra__nombre_palabra"),
+ producto=F(
+ "id_dato__id_calificacion__id_producto__codigoProducto"),
+ valor_decimal=F("valor"),
+ )
+ )
+
+ # Calculate averages per product per word
+ word_sums = defaultdict(lambda: defaultdict(list))
+ all_words_set = set()
+
+ for item in data:
+ palabra = item["palabra"]
+ producto = item["producto"]
+ valor = item["valor_decimal"]
+ word_sums[producto][palabra].append(valor)
+ all_words_set.add(palabra)
+
+ # Calculate averages
+ word_averages = {}
+ for producto, palabras in word_sums.items():
+ word_averages[producto] = {}
+ for palabra, valores in palabras.items():
+ word_averages[producto][palabra] = sum(valores) / len(valores)
+
+ # Get vocabulary info if exists
+ vocabulary_info = self.getVocabularyInfo(session_b.tecnica)
+
+ return {
+ "word_averages": word_averages,
+ "all_words": sorted(all_words_set),
+ "vocabulary_info": vocabulary_info,
+ }
+
+ def getCombinedDataForEscalas(self, session_b: SesionSensorial):
+ """Get combined data for Escalas technique (averages across repetitions)"""
+
+ # Get all ratings for session B
+ ratings_b = Calificacion.objects.filter(id_tecnica=session_b.tecnica)
+
+ # Get decimal values grouped by repetition
+ data = (
+ ValorDecimal.objects
+ .filter(id_dato__id_calificacion__in=ratings_b)
+ .values(
+ palabra=F("id_dato__id_palabra__nombre_palabra"),
+ producto=F(
+ "id_dato__id_calificacion__id_producto__codigoProducto"),
+ repeticion=F("id_dato__id_calificacion__num_repeticion"),
+ valor_decimal=F("valor"),
+ )
+ )
+
+ # Calculate averages per repetition (like RATA), then average across repetitions
+ # Structure: {producto: {repeticion: {palabra: [valores]}}}
+ repetition_values = defaultdict(
+ lambda: defaultdict(lambda: defaultdict(list)))
+ all_words_set = set()
+
+ for item in data:
+ palabra = item["palabra"]
+ producto = item["producto"]
+ repeticion = item["repeticion"]
+ valor = item["valor_decimal"]
+ # Collect all values for averaging
+ repetition_values[producto][repeticion][palabra].append(valor)
+ all_words_set.add(palabra)
+
+ # Calculate average per repetition, then average across repetitions
+ word_averages = {}
+ for producto, repeticiones in repetition_values.items():
+ word_averages[producto] = {}
+ # Get all words for this product across all repetitions
+ all_product_words = set()
+ for rep_words in repeticiones.values():
+ all_product_words.update(rep_words.keys())
+
+ # Calculate average for each word
+ for palabra in all_product_words:
+ # Get average for each repetition
+ rep_averages = []
+ for rep in repeticiones.keys():
+ if palabra in repeticiones[rep]:
+ valores = repeticiones[rep][palabra]
+ rep_averages.append(sum(valores) / len(valores))
+
+ # Average the repetition averages
+ if rep_averages:
+ word_averages[producto][palabra] = sum(
+ rep_averages) / len(rep_averages)
+
+ # Get scale and vocabulary info
+ scale = Escala.objects.get(tecnica=session_b.tecnica)
+ scale_info = None
+ if scale:
+ scale_info = {
+ "type": scale.id_tipo_escala.nombre_escala,
+ "size": scale.longitud
+ }
+
+ vocabulary_info = self.getVocabularyInfo(session_b.tecnica)
+
+ return {
+ "word_averages": word_averages,
+ "all_words": sorted(all_words_set),
+ "scale_info": scale_info,
+ "vocabulary_info": vocabulary_info,
+ "num_repetitions": session_b.tecnica.repeticion,
+ }
+
+ def getVocabularyInfo(self, tecnica):
+ es_vocabulario = EsVocabulario.objects.filter(
+ id_tecnica=tecnica).first()
+ if es_vocabulario:
+ vocabulario = es_vocabulario.id_vocabulario
+ return {
+ "nombre": vocabulario.nombre_vocabulario,
+ }
+ return None
diff --git a/tecnicas/controllers/views_controller/session_management/details/details_pf_controller.py b/tecnicas/controllers/views_controller/session_management/details/details_pf_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..48c38186db76d9e44fdcfa04680a71d7594053d7
--- /dev/null
+++ b/tecnicas/controllers/views_controller/session_management/details/details_pf_controller.py
@@ -0,0 +1,215 @@
+from tecnicas.models import SesionSensorial, ListaPalabras, Calificacion, Catador
+from tecnicas.controllers import DatoController
+from tecnicas.utils import defaultdict_to_dict
+from .details_controller import DetallesController
+from collections import defaultdict
+
+
+class DetallesPFController(DetallesController):
+ skip_repetition = 2
+
+ def __init__(self, session: SesionSensorial):
+ super().__init__(session)
+ self.url_template = "tecnicas/manage_sesions/details-session-pf.html"
+ self.url_next = "cata_system:monitor_sesion"
+
+ def getContext(self):
+ technique = self.session.tecnica
+
+ self.context = {
+ "sesion": self.session,
+ "use_technique": technique,
+ "tipo_escala": technique.escala_tecnica.id_tipo_escala.nombre_escala,
+ "repeticiones_max": technique.repeticiones_max - self.skip_repetition
+ }
+
+ # Definir el estado de la sesion
+ rep = technique.repeticion
+ activate = self.session.activo
+ self.context["estado"] = self.getStatus(rep, activate)
+
+ self.getDataPhases()
+
+ self.isEndSession()
+
+ return self.context
+
+ def isEndSession(self):
+ current_rep = self.session.tecnica.repeticion - self.skip_repetition
+ max_rep = self.session.tecnica.repeticiones_max - self.skip_repetition
+ self.context["finished"] = current_rep >= max_rep
+
+ def getDataPhases(self):
+ curren_repetition = self.session.tecnica.repeticion
+
+ if curren_repetition == 1:
+ self.context["fisrt_phase"] = self.getDataFirstPhase()
+ self.context["repeticion"] = 0
+
+ elif curren_repetition == 2:
+ self.context["fisrt_phase"] = self.getDataFirstPhase()
+ self.context["second_phase"] = self.getDataSecondPhase()
+ self.context["repeticion"] = 0
+
+ elif curren_repetition >= 3:
+ self.context["fisrt_phase"] = self.getDataFirstPhase()
+ self.context["second_phase"] = self.getDataSecondPhase()
+ self.context["data_ratings"] = self.getDataRatings()
+ self.context["repeticion"] = self.session.tecnica.repeticion - self.skip_repetition
+
+ return self.context
+
+ def getDataFirstPhase(self):
+ lists_testers = ListaPalabras.objects.filter(
+ tecnica=self.session.tecnica,
+ es_final=False
+ )
+
+ result = []
+ for list in lists_testers:
+ try:
+ username = list.catador.user.username
+ except Exception:
+ username = None
+
+ words_qs = list.palabras.all()
+ words = []
+ for p in words_qs:
+ nombre = getattr(p, 'nombre_palabra', None)
+ words.append({
+ 'id': getattr(p, 'id', None),
+ 'nombre_palabra': nombre
+ })
+
+ result.append({
+ 'username': username,
+ 'words': words
+ })
+
+ return result
+
+ def getDataSecondPhase(self):
+ lists_testers = ListaPalabras.objects.filter(
+ tecnica=self.session.tecnica,
+ es_final=True
+ )
+
+ result = []
+ for list in lists_testers:
+ try:
+ username = list.catador.user.username
+ except Exception:
+ username = None
+
+ words_qs = list.palabras.all()
+ words = []
+ for p in words_qs:
+ nombre = getattr(p, 'nombre_palabra', None)
+ words.append({
+ 'id': getattr(p, 'id', None),
+ 'nombre_palabra': nombre
+ })
+
+ result.append({
+ 'username': username,
+ 'words': words
+ })
+
+ return result
+
+ def getDataRatings(self):
+ technique = self.session.tecnica
+
+ if technique.repeticion > 3:
+ return self.getDataRatingsFinal()
+
+ elif technique.repeticion == 3:
+ return self.getDataRatingsInitial()
+
+ def getStatus(self, rep: int, activate: bool):
+ status = ""
+
+ if rep == 0 and not activate:
+ status = "Listo para crear listas iniciales"
+
+ elif rep == 1 and activate:
+ status = "En primera fase, creación de listas iniciales"
+ elif rep == 1 and not activate:
+ status = "Listo para crear listas finales"
+
+ elif rep == 2 and activate:
+ status = "En segunda fase, creación de listas finales"
+ elif rep == 2 and not activate:
+ status = "Listo para calificaciones"
+
+ elif rep > 2 and not activate:
+ status = "Listo para calificaciones"
+ elif rep > 2 and activate:
+ status = "Catadores calificando"
+
+ return status
+
+ def getDataRatingsInitial(self):
+ ratings = list(Calificacion.objects.filter(id_tecnica=self.session.tecnica, num_repeticion=3))
+
+ if ratings:
+ raw_data = DatoController.getWordValuesForConvecional(
+ technique=self.session.tecnica,
+ ratings=ratings
+ )
+
+ structured_data = defaultdict(lambda: defaultdict(list))
+
+ for item in raw_data:
+ prod_code = item["producto_code"]
+ username = item["usuario_catador"]
+
+ structured_data[prod_code][username].append({
+ "palabra": item["nombre_palabra"],
+ "valor": item["dato_valor"]
+ })
+
+ return defaultdict_to_dict(structured_data)
+
+ def getDataRatingsFinal(self):
+ lists_words_testers = self.context["second_phase"]
+ technique = self.session.tecnica
+
+ ratings_for_tester = []
+
+ for list_tester in lists_words_testers:
+ tester_username = list_tester["username"]
+ # Se recuperan las calificaciones
+ ratings_for_repetition = []
+
+ ratings = list(Calificacion.objects.filter(
+ id_tecnica=technique, id_catador__user__username=tester_username))
+
+ if not ratings:
+ continue
+
+ data = DatoController.getWordValuesPF(
+ ratings=ratings, technique=technique, tester=Catador.objects.get(user__username=tester_username))
+
+ ratings_for_repetition = defaultdict(lambda: defaultdict(list))
+
+ # Estructurar los datos
+ for item in data:
+ rep = item["repeticion"]
+ prod = item["producto_code"]
+
+ ratings_for_repetition[rep-2][prod].append({
+ "nombre_palabra": item["nombre_palabra"],
+ "dato_valor": item["dato_valor"]
+ })
+
+ ratings_for_tester.append(
+ {
+ "tester": tester_username,
+ "ratings": defaultdict_to_dict(
+ ratings_for_repetition),
+ "words": list_tester["words"]
+ }
+ )
+
+ return ratings_for_tester
\ No newline at end of file
diff --git a/tecnicas/controllers/views_controller/session_management/details/details_rata_controller.py b/tecnicas/controllers/views_controller/session_management/details/details_rata_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..71dae2b469f24c2dd1a2ec969a32a329d62c574a
--- /dev/null
+++ b/tecnicas/controllers/views_controller/session_management/details/details_rata_controller.py
@@ -0,0 +1,7 @@
+from tecnicas.models import SesionSensorial
+from .details_controller import DetallesController
+
+
+class DetallesRATAController(DetallesController):
+ def __init__(self, session: SesionSensorial):
+ super().__init__(session)
diff --git a/tecnicas/controllers/views_controller/session_management/details/details_sort_controller.py b/tecnicas/controllers/views_controller/session_management/details/details_sort_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..542df448c230782658ce5d12142c38e733b60405
--- /dev/null
+++ b/tecnicas/controllers/views_controller/session_management/details/details_sort_controller.py
@@ -0,0 +1,91 @@
+from .details_controller import DetallesController
+from tecnicas.models import SesionSensorial, GrupoProducto, Producto, Participacion
+from collections import defaultdict
+
+
+class DetallesSortController(DetallesController):
+ def __init__(self, session: SesionSensorial):
+ super().__init__(session)
+ self.url_template = "tecnicas/manage_sesions/details-session-sort.html"
+ self.url_next = "cata_system:monitor_sesion"
+
+ def getContext(self):
+ technique = self.session.tecnica
+
+ finished = False
+ status = ""
+
+ if technique.repeticion < technique.repeticiones_max and not self.session.activo:
+ status = "En espera para iniciar la sesión"
+ elif technique.repeticion >= technique.repeticiones_max and not self.session.activo:
+ status = "Esta sesión ha sido finalizada"
+ finished = True
+ else:
+ status = "La sesión está en progreso"
+
+ self.context = {
+ "sesion": self.session,
+ "technique": technique,
+ "status": status,
+ "finished": finished
+ }
+
+ self.context["data_groups"] = {
+ "data": self.setDataSort(),
+ "testers": self.setHeaders()
+ }
+
+ return self.context
+
+ def setDataSort(self):
+ data = []
+ technique = self.session.tecnica
+
+ products = Producto.objects.filter(id_tecnica=technique)
+
+ groups = GrupoProducto.objects.select_related("catador").filter(
+ tecnica=technique
+ )
+
+ if len(groups):
+ self.context["there_data"] = True
+ else:
+ self.context["there_data"] = False
+ return []
+
+ for product in products:
+ product_data = {
+ "codigo_producto": product.codigoProducto,
+ "palabras": {}
+ }
+
+ related_groups = groups.filter(productos=product).select_related(
+ "catador__user"
+ ).prefetch_related("palabras")
+
+ data_words = defaultdict(set)
+
+ for group in related_groups:
+ catador_username = group.catador.user.username
+
+ for word in group.palabras.all():
+ data_words[catador_username].add(word.nombre_palabra)
+
+ product_data["palabras"] = {
+ username: list(words)
+ for username, words in data_words.items()
+ }
+
+ data.append(product_data)
+
+ return data
+
+ def setHeaders(self):
+ participacions = list(Participacion.objects.filter(
+ tecnica=self.session.tecnica
+ ).only("catador").select_related("catador__user"))
+
+ testers = [
+ participacion.catador.user.username for participacion in participacions]
+
+ return testers
diff --git a/tecnicas/controllers/views_controller/session_management/details_controller.py b/tecnicas/controllers/views_controller/session_management/details_controller.py
deleted file mode 100644
index b98cfe897ed711c8084f45eb50e152f5641fc229..0000000000000000000000000000000000000000
--- a/tecnicas/controllers/views_controller/session_management/details_controller.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from tecnicas.models import SesionSensorial, Presentador, Tecnica
-from tecnicas.utils import controller_error
-
-
-class DetallesController():
- url_template = "tecnicas/manage_sesions/detalles-sesion.html"
-
- def __init__(self, session: SesionSensorial):
- self.session = session
-
- def deleteSesorialSession(self):
- technique = Tecnica.objects.get(id=self.session.tecnica.id)
- technique.delete()
\ No newline at end of file
diff --git a/tecnicas/controllers/views_controller/session_management/details_rata_controller.py b/tecnicas/controllers/views_controller/session_management/details_rata_controller.py
deleted file mode 100644
index c0b437791c57de37b9354b41e7d18bdd33417635..0000000000000000000000000000000000000000
--- a/tecnicas/controllers/views_controller/session_management/details_rata_controller.py
+++ /dev/null
@@ -1,56 +0,0 @@
-from django.http import HttpRequest
-from django.shortcuts import render, redirect
-from tecnicas.models import SesionSensorial
-from tecnicas.controllers import PalabrasController, DatoController, CalificacionController
-from tecnicas.utils import defaultdict_to_dict
-from .details_controller import DetallesController
-from collections import defaultdict
-
-
-class DetallesRATAController(DetallesController):
- def __init__(self, session: SesionSensorial):
- super().__init__(session)
-
- def getResponse(self, request: HttpRequest, error: str = ""):
- context = self.getContext()
- if error != "" or error:
- context["error"] = error
- return render(
- request, self.url_template, context)
-
- def getContext(self):
- self.context = {}
- self.context["sesion"] = self.session
- self.words = PalabrasController.getWordsInTechnique(
- self.session.tecnica)
- self.context["palabras"] = [word.nombre_palabra for word in self.words]
-
- ratings_for_repetition = []
-
- ratings = CalificacionController.getRatingsByTechnique(
- technique=self.session.tecnica)
-
- if isinstance(ratings, dict) or not ratings:
- self.context["calificaciones"] = ratings_for_repetition
- self.context["existen_calificaciones"] = False
- return self.context
-
- data = DatoController.getWordValuesForConvecional(
- ratings=ratings, technique=self.session.tecnica)
-
- ratings_for_repetition = defaultdict(
- lambda: defaultdict(lambda: defaultdict(list)))
-
- for item in data:
- user = item["usuarioCatador"]
- rep = item["repeticion"]
- prod = item["producto_code"]
-
- ratings_for_repetition[rep][user][prod].append({
- "nombre_palabra": item["nombre_palabra"],
- "dato_valor": item["dato_valor"]
- })
-
- self.context["calificaciones"] = defaultdict_to_dict(
- ratings_for_repetition)
- self.context["existen_calificaciones"] = True
diff --git a/tecnicas/controllers/views_controller/session_management/monitor_controller.py b/tecnicas/controllers/views_controller/session_management/monitor/monitor_controller.py
similarity index 64%
rename from tecnicas/controllers/views_controller/session_management/monitor_controller.py
rename to tecnicas/controllers/views_controller/session_management/monitor/monitor_controller.py
index b0f2a19b1845b605f1bd174399e34b300e7da89b..16aa77587372d74b5abcf710927f69fce4143cc2 100644
--- a/tecnicas/controllers/views_controller/session_management/monitor_controller.py
+++ b/tecnicas/controllers/views_controller/session_management/monitor/monitor_controller.py
@@ -1,8 +1,7 @@
from django.http import HttpRequest
-from django.shortcuts import render
-from tecnicas.models import SesionSensorial, Producto, EsAtributo, EsVocabulario
-from tecnicas.controllers import ParticipacionController, SesionController
-from tecnicas.utils import controller_error
+from django.shortcuts import render, redirect
+from django.urls import reverse
+from tecnicas.models import SesionSensorial, Producto, EsAtributo, EsVocabulario, Participacion
class MonitorController():
@@ -12,9 +11,21 @@ class MonitorController():
def __init__(self, session: SesionSensorial):
self.sensorial_session = session
+ def controllPostFinishSession(self, request: HttpRequest):
+ (is_all_end, message) = self.checkAllFinish()
+ if not is_all_end:
+ self.setContext()
+ self.context["error"] = message
+ return render(request, self.url_view, self.context)
+ self.finishSession()
+ return redirect(reverse(self.previus_view, kwargs={"session_code": self.sensorial_session.codigo_sesion}))
+
+ def checkAllFinish(self) -> (bool, str):
+ return (False, "Función sin implementar")
+
def setContext(self):
- self.participations = ParticipacionController.getParticipationsInTechinique(
- self.sensorial_session.tecnica)
+ self.participations = Participacion.objects.filter(
+ tecnica=self.sensorial_session.tecnica)
self.context = {
"code_session": self.sensorial_session.codigo_sesion,
@@ -26,7 +37,7 @@ class MonitorController():
"use_technique": self.sensorial_session.tecnica.tipo_tecnica.nombre_tecnica
}
- def controlGetResponse(self, request: HttpRequest, error: str = "", message: str = ""):
+ def controllGetResponse(self, request: HttpRequest, error: str = "", message: str = ""):
self.setContext()
if error != "" or error:
@@ -36,7 +47,7 @@ class MonitorController():
return render(request, self.url_view, self.context)
- def getExpectedRatingsEscalasRapida(self):
+ def getExpectedRatings(self):
num_products = Producto.objects.filter(
id_tecnica=self.sensorial_session.tecnica).count()
style_words = self.sensorial_session.tecnica.id_estilo
@@ -52,8 +63,6 @@ class MonitorController():
return num_products * num_words
def finishSession(self):
- response = SesionController.finishRepetion(self.sensorial_session)
- if isinstance(response, dict):
- return controller_error(response["error"])
- self.sensorial_session.refresh_from_db()
+ self.sensorial_session.activo = False
+ self.sensorial_session.save()
return self.sensorial_session
diff --git a/tecnicas/controllers/views_controller/session_management/monitor_escalas_controller.py b/tecnicas/controllers/views_controller/session_management/monitor/monitor_escalas_controller.py
similarity index 60%
rename from tecnicas/controllers/views_controller/session_management/monitor_escalas_controller.py
rename to tecnicas/controllers/views_controller/session_management/monitor/monitor_escalas_controller.py
index 36b976bf9cc8038f71021cbdbdfc6413bf436fab..59c7e853d9bb96e726ab4892126ed9fb1e8f544f 100644
--- a/tecnicas/controllers/views_controller/session_management/monitor_escalas_controller.py
+++ b/tecnicas/controllers/views_controller/session_management/monitor/monitor_escalas_controller.py
@@ -1,6 +1,3 @@
-from django.http import HttpRequest
-from django.shortcuts import render, redirect
-from django.urls import reverse
from tecnicas.models import Dato, Participacion
from tecnicas.controllers import SesionController
from .monitor_controller import MonitorController
@@ -12,23 +9,10 @@ class MonitorEscalasController(MonitorController):
self.url_view = "tecnicas/manage_sesions/monitor-sesion.html"
self.previus_view = "cata_system:detalles_sesion"
- def controllPostFinishSession(self, request: HttpRequest):
- self.setContext()
- (is_all_end, message) = self.checkAllFinish()
- if not is_all_end:
- self.context["error"] = message
- return render(request, self.url_view, self.context)
- response = self.finishSession()
- if isinstance(response, dict):
- self.context["error"] = response["error"]
- return render(request, self.url_view, self.context)
- self.context["message"] = message
- return redirect(reverse(self.previus_view, kwargs={"session_code": self.sensorial_session.codigo_sesion}))
-
def checkAllFinish(self):
technique = self.sensorial_session.tecnica
- expected_ratings_repetition = self.getExpectedRatingsEscalasRapida()
+ expected_ratings_repetition = self.getExpectedRatings()
all_participations = list(
Participacion.objects.filter(tecnica=technique))
diff --git a/tecnicas/controllers/views_controller/session_management/monitor/monitor_napping_controller.py b/tecnicas/controllers/views_controller/session_management/monitor/monitor_napping_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..e689f6b1f2475066c903682d6f12be3b3789b7e7
--- /dev/null
+++ b/tecnicas/controllers/views_controller/session_management/monitor/monitor_napping_controller.py
@@ -0,0 +1,35 @@
+from tecnicas.models import SesionSensorial
+from tecnicas.models import Participacion, TecnicaModalidad
+from .monitor_controller import MonitorController
+
+
+class MonitorNappingController(MonitorController):
+ def __init__(self, session: SesionSensorial):
+ super().__init__(session)
+ self.url_view = "tecnicas/manage_sesions/monitor-session-sort.html"
+ self.previus_view = "cata_system:detalles_sesion"
+
+ def checkAllFinish(self) -> (bool, str):
+ technique = self.sensorial_session.tecnica
+
+ num_participations = Participacion.objects.filter(
+ tecnica=technique).count()
+
+ if num_participations < technique.limite_catadores:
+ return (False, "No se ha alcanzado el número máximo de catadores")
+
+ unfinished_participations = Participacion.objects.filter(
+ tecnica=technique, finalizado=False).count()
+
+ if unfinished_participations > 0:
+ return (False, "No todos los catadores han finalizado su evaluación")
+
+ return (True, "Puedes finalizar la sesión")
+
+ def finishSession(self):
+ technique = self.sensorial_session.tecnica
+ technique.repeticion = 1
+ technique.save()
+ self.sensorial_session.activo = False
+ self.sensorial_session.save()
+ return self.sensorial_session
diff --git a/tecnicas/controllers/views_controller/session_management/monitor/monitor_pf_controller.py b/tecnicas/controllers/views_controller/session_management/monitor/monitor_pf_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..d15d1356635910a50e8ea628a7d3fcac337952a4
--- /dev/null
+++ b/tecnicas/controllers/views_controller/session_management/monitor/monitor_pf_controller.py
@@ -0,0 +1,72 @@
+from tecnicas.models import Dato, Participacion, Catador, ListaPalabras, Producto
+from tecnicas.controllers import SesionController
+from .monitor_controller import MonitorController
+
+
+class MonitorPFController(MonitorController):
+ def __init__(self, session: SesionController):
+ super().__init__(session)
+ self.url_view = "tecnicas/manage_sesions/monitor-session-pf.html"
+ self.previus_view = "cata_system:detalles_sesion"
+
+ def checkAllFinish(self):
+ rep = self.sensorial_session.tecnica.repeticion
+
+ finish_data = ()
+
+ if rep == 1 or rep == 2:
+ finish_data = self.checkFinishFirstPhase()
+ elif rep >= 3:
+ finish_data = self.checkFinishRepetition()
+
+ return finish_data
+
+ def checkFinishFirstPhase(self):
+ num_paricipations = Participacion.objects.filter(
+ tecnica=self.sensorial_session.tecnica).count()
+ if num_paricipations < self.sensorial_session.tecnica.limite_catadores:
+ return (False, "No se ha alcanzado el número máximo de catadores")
+
+ unfinished_participations = Participacion.objects.filter(
+ tecnica=self.sensorial_session.tecnica, finalizado=False).count()
+ if unfinished_participations:
+ return (False, "No todos los catadores han finalizado su evaluación")
+
+ return (True, "Puedes finalizar la sesión")
+
+ def checkFinishRepetition(self) -> tuple[bool, str]:
+ technique = self.sensorial_session.tecnica
+
+ # Revisar numero de catadores sea alcanzado
+ all_participations = list(
+ Participacion.objects.filter(tecnica=technique))
+ if len(all_participations) < technique.limite_catadores:
+ return (False, "No se ha alcanzado el número máximo de Catadores")
+
+ # Revisar que cada catador haya terminado de calificar sus palabras
+ for particiapation in all_participations:
+ expected_ratings_repetition = self.getExpectedRatings(
+ tester=particiapation.catador)
+
+ num_ratings_now = Dato.objects.filter(
+ id_calificacion__num_repeticion=technique.repeticion,
+ id_calificacion__id_catador=particiapation.catador,
+ id_calificacion__id_tecnica=technique
+ ).count()
+
+ if num_ratings_now < expected_ratings_repetition:
+ return (False, "No todos los catadores han finalizado su evaluación")
+
+ return (True, "Puedes finalizar la sesión")
+
+ def getExpectedRatings(self, tester: Catador):
+ num_words = ListaPalabras.objects.get(
+ tecnica=self.sensorial_session.tecnica,
+ catador=tester,
+ es_final=True
+ ).palabras.all().count()
+
+ num_products = Producto.objects.filter(
+ id_tecnica=self.sensorial_session.tecnica).count()
+
+ return num_products * num_words
diff --git a/tecnicas/controllers/views_controller/session_management/monitor_rata_controller.py b/tecnicas/controllers/views_controller/session_management/monitor/monitor_rata_controller.py
similarity index 57%
rename from tecnicas/controllers/views_controller/session_management/monitor_rata_controller.py
rename to tecnicas/controllers/views_controller/session_management/monitor/monitor_rata_controller.py
index 3337848fe4bc36561fc5968e68c71a5e71f08a28..2629e4dde97037380cadf500456723ba729ba03f 100644
--- a/tecnicas/controllers/views_controller/session_management/monitor_rata_controller.py
+++ b/tecnicas/controllers/views_controller/session_management/monitor/monitor_rata_controller.py
@@ -1,6 +1,3 @@
-from django.http import HttpRequest
-from django.shortcuts import render, redirect
-from django.urls import reverse
from tecnicas.models import Dato, Participacion
from tecnicas.controllers import SesionController
from .monitor_controller import MonitorController
@@ -12,23 +9,10 @@ class MonitorRATAController(MonitorController):
self.url_view = "tecnicas/manage_sesions/monitor-sesion.html"
self.previus_view = "cata_system:detalles_sesion"
- def controllPostFinishSession(self, request: HttpRequest):
- self.setContext()
- (is_all_end, message) = self.checkAllFinish()
- if not is_all_end:
- self.context["error"] = message
- return render(request, self.url_view, self.context)
- response = self.finishSession()
- if isinstance(response, dict):
- self.context["error"] = response["error"]
- return render(request, self.url_view, self.context)
- self.context["message"] = message
- return redirect(reverse(self.previus_view, kwargs={"session_code": self.sensorial_session.codigo_sesion}))
-
def checkAllFinish(self):
technique = self.sensorial_session.tecnica
- expected_ratings_repetition = self.getExpectedRatingsEscalasRapida()
+ expected_ratings_repetition = self.getExpectedRatings()
all_participations = list(
Participacion.objects.filter(tecnica=technique))
diff --git a/tecnicas/controllers/views_controller/session_management/monitor/monitor_sort_controller.py b/tecnicas/controllers/views_controller/session_management/monitor/monitor_sort_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..faf9890bf613eb704b8a9556d6f4c54307622891
--- /dev/null
+++ b/tecnicas/controllers/views_controller/session_management/monitor/monitor_sort_controller.py
@@ -0,0 +1,27 @@
+from tecnicas.models import SesionSensorial
+from tecnicas.models import Participacion
+from .monitor_controller import MonitorController
+
+
+class MonitorSortController(MonitorController):
+ def __init__(self, session: SesionSensorial):
+ super().__init__(session)
+ self.url_view = "tecnicas/manage_sesions/monitor-session-sort.html"
+ self.previus_view = "cata_system:detalles_sesion"
+
+ def checkAllFinish(self):
+ technique = self.sensorial_session.tecnica
+
+ num_participations = Participacion.objects.filter(
+ tecnica=technique).count()
+
+ if num_participations < technique.limite_catadores:
+ return (False, "No se ha alcanzado el número máximo de catadores")
+
+ unfinished_participations = Participacion.objects.filter(
+ tecnica=technique, finalizado=False).count()
+
+ if unfinished_participations > 0:
+ return (False, "No todos los catadores han finalizado su evaluación")
+
+ return (True, "Puedes finalizar la sesión")
\ No newline at end of file
diff --git a/tecnicas/controllers/views_controller/sessions_tester/convencional_scales_controller.py b/tecnicas/controllers/views_controller/sessions_tester/convencional_scales_controller.py
deleted file mode 100644
index 897fa25cd4bade5a006e973de0818f5e4fbba06d..0000000000000000000000000000000000000000
--- a/tecnicas/controllers/views_controller/sessions_tester/convencional_scales_controller.py
+++ /dev/null
@@ -1,158 +0,0 @@
-from django.http import HttpRequest
-from django.shortcuts import redirect, render
-from django.urls import reverse
-from tecnicas.models import SesionSensorial, Catador, Participacion, Producto, Calificacion, Palabra
-from tecnicas.controllers import PosicionController, CalificacionController, ParticipacionController, PalabrasController, EscalaController, DatoController
-
-
-class ConvencionalScalesController:
- context = {}
- current_directory = "tecnicas/forms_tester/convencional.html"
- previus_directory = "cata_system:catador_init_session"
-
- def __init__(self, sensorial_session: SesionSensorial, user_tester: Catador):
- self.tester = user_tester
- self.session = sensorial_session
-
- def controllGetEscalas(self, request: HttpRequest):
- technique = self.session.tecnica
- self.participation = Participacion.objects.get(
- tecnica=technique, catador=request.user.user_catador)
-
- ctx = self.context
- ctx["session"] = self.session
-
- # Obtener posiciones y palabras de la técnica
- positions_in_order = PosicionController.getPostionsInOrder(
- id_order=request.session["id_order"])
- aligned_positions_in_order = sorted(
- positions_in_order, key=lambda p: p.posicion)
- words = PalabrasController.getWordsInTechnique(technique=technique)
-
- # Comprobar siguiente posición sin calificar
- (next_position, end_products) = CalificacionController.checkPositionWithoutRating(
- positions=aligned_positions_in_order,
- user_cata=request.user.user_catador,
- repetition=technique.repeticion,
- technique=technique,
- num_words=len(words)
- )
-
- # Si no hay productos se finaliza la sesion
- if end_products:
- ParticipacionController.finishSession(self.participation)
- params = {"code_sesion": self.session.codigo_sesion}
- return redirect(reverse('cata_system:catador_init_session', kwargs=params))
-
- # Si devuelve una lista, tomar el primer elemento
- if isinstance(next_position, list):
- next_position = next_position[0]
-
- # Producto a calificar ahora
- product = next_position.id_producto
- ctx["product"] = product
-
- # Revisar las palabras para calificar
- try:
- rating = Calificacion.objects.get(
- num_repeticion=technique.repeticion,
- id_producto=product,
- id_tecnica=technique,
- id_catador=self.tester
- )
- there_rating = True
- except Calificacion.DoesNotExist:
- there_rating = False
-
- # Si no hay calificaciones previas, usar todas las palabras
- if not there_rating:
- ctx["words"] = words
- else:
- ratings_product = rating.dato_calificacion.all()
- # Filtrar palabras que faltan
- words_to_use = PalabrasController.getWordsWithoutData(
- recoreded_data=ratings_product,
- words=words
- )
- ctx["words"] = words_to_use
-
- # Escala y etiquetas relacionadas
- scale = EscalaController.getScaleByTechnique(technique=technique)
- ctx["scale"] = scale
- ctx["type_scale"] = scale.id_tipo_escala.nombre_escala
- ctx["tags"] = EscalaController.getRelatedTagsInScale(scale=scale)
-
- return render(request, self.current_directory, ctx)
-
- def controllGetRATA(self, request: HttpRequest):
- technique = self.session.tecnica
- self.participation = Participacion.objects.get(
- tecnica=technique, catador=request.user.user_catador)
-
- self.context["session"] = self.session
-
- products_in_technique = Producto.objects.filter(id_tecnica=technique)
-
- words = PalabrasController.getWordsInTechnique(technique=technique)
-
- use_product: Producto = None
- use_words: list[Palabra] = None
-
- # Revisamos el producto que le falten calificaciones
- for current_product in products_in_technique:
- try:
- rating = Calificacion.objects.get(
- num_repeticion=technique.repeticion,
- id_producto=current_product,
- id_tecnica=technique,
- id_catador=self.tester
- )
- there_rating = True
- except Calificacion.DoesNotExist:
- there_rating = False
-
- # Si no hay calificacion mandamos el producto actual y todas la palabras
- if not there_rating:
- use_product = current_product
- use_words = words
- break
-
- # Obtener los datos asociados para la calificacion para ver que palabras quedan por calificar
- recoreded_data = rating.dato_calificacion.all()
-
- if not recoreded_data:
- # Si no hay datos entonces devolver el producto con todas las palabras
- use_product = current_product
- use_words = words
- break
- else:
- words_to_use = PalabrasController.getWordsWithoutData(
- recoreded_data=recoreded_data, words=words)
-
- # Si quedan palabras por calificar mandar las palabras con el producto
- if not isinstance(words_to_use, dict) and words_to_use:
- use_product = current_product
- use_words = words_to_use
- break
-
- # Si no hay producto que falta por calificar finalizar sesion para el Catador
- if not use_product:
- updated_participation = ParticipacionController.finishSession(
- self.participation)
- params = {
- "code_sesion": self.session.codigo_sesion
- }
- return redirect(reverse(self.previus_directory, kwargs=params))
-
- self.context["product"] = use_product
- self.context["words"] = use_words
-
- # Agregar informacion de la escala
- scale = EscalaController.getScaleByTechnique(technique=technique)
- self.context["scale"] = scale
- self.context["type_scale"] = scale.id_tipo_escala.nombre_escala
-
- use_tags = EscalaController.getRelatedTagsInScale(scale=scale)
- self.context["tags"] = use_tags
-
- return render(request, self.current_directory, self.context)
diff --git a/tecnicas/controllers/views_controller/sessions_tester/init_session_tester_controller.py b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_controller.py
similarity index 51%
rename from tecnicas/controllers/views_controller/sessions_tester/init_session_tester_controller.py
rename to tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_controller.py
index 29102a123cf00bdd6387553378bd3eef0f915724..3aae41371be88f624386caa5841f8277d6495eed 100644
--- a/tecnicas/controllers/views_controller/sessions_tester/init_session_tester_controller.py
+++ b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_controller.py
@@ -1,96 +1,23 @@
-from django.db import transaction
from django.http import HttpRequest
from django.shortcuts import render, redirect
from django.urls import reverse
-from tecnicas.models import Catador, SesionSensorial, Orden, Participacion, Producto, EsAtributo, Calificacion, EsVocabulario, Dato
-from tecnicas.controllers import ParticipacionController
+from django.db import transaction
+from tecnicas.models import Catador, SesionSensorial, Orden, Participacion, Producto, EsAtributo, EsVocabulario, Dato, ListaPalabras
from tecnicas.utils import controller_error, shuffleArray
-class InitSessionTesterController():
+class InitSessionController():
tester: Catador
session: SesionSensorial
order: Orden | dict
- current_direction = "tecnicas/forms_tester/init_session.html"
+ current_direction: str
+ current_direction = "tecnicas/forms_tester/init_scales_test.html"
escalas_direction = "cata_system:session_convencional"
def __init__(self, sensorial_session: SesionSensorial, user_tester: Catador):
self.tester = user_tester
self.session = sensorial_session
- def controllGetEscalas(self, request: HttpRequest):
- context = {
- "session": self.session,
- "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
- }
-
- order = self.checkAndAssignOrder()
- if isinstance(order, dict):
- context["error"] = order["error"]
- return render(request, self.current_direction, context)
-
- is_end = self.isEndedSessionEscalas()
-
- request.session["id_order"] = order.id
- context["has_ended"] = is_end
-
- if is_end:
- context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
-
- if "error" in request.GET:
- context["error"] = request.GET["error"]
-
- return render(request, self.current_direction, context)
-
- def controllPostEscalas(self, request: HttpRequest):
- context = {
- "session": self.session,
- "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
- }
-
- if request.POST["action"] == "start_posting":
- parameters = {
- "code_sesion": self.session.codigo_sesion
- }
-
- is_end = self.isEndedSessionEscalas()
- if is_end:
- context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
- return render(request, self.current_direction, context)
-
- update_participation = ParticipacionController.enterSession(
- tester=request.user.user_catador, session=self.session)
- if isinstance(update_participation, dict):
- context["error"] = update_participation["error"]
- return render(request, self.current_direction, context)
-
- request.session["id_participation"] = update_participation.id
- return redirect(reverse(self.escalas_direction, kwargs=parameters))
- elif request.POST["action"] == "exit_session":
- response = ParticipacionController.outSession(
- tester=request.user.user_catador, session=self.session)
- if isinstance(response, dict):
- context["error"] = response["error"]
- return render(request, self.current_direction, context)
- else:
- context["error"] = "Acción sin especificar"
- return render(request, self.current_direction, context)
-
- def controllGetRATA(self, request: HttpRequest):
- context = {
- "session": self.session,
- "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
- }
-
- is_end = self.isEndedSessionEscalas()
-
- context["has_ended"] = is_end
-
- if is_end:
- context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
-
- return render(request, self.current_direction, context)
-
def assignOrder(self):
with transaction.atomic():
orders_without_tester = list(Orden.objects.select_for_update().filter(
@@ -117,7 +44,7 @@ class InitSessionTesterController():
return create
return self.order_to_assign
- def isEndedSessionEscalas(self):
+ def isEndedSession(self):
try:
participation = Participacion.objects.get(
catador=self.tester, tecnica=self.session.tecnica)
diff --git a/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_escalas_controller.py b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_escalas_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..2fb4e970f749193123cb785775eed700a21cfa7a
--- /dev/null
+++ b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_escalas_controller.py
@@ -0,0 +1,82 @@
+from django.http import HttpRequest
+from django.shortcuts import render, redirect
+from django.urls import reverse
+from tecnicas.models import Catador, SesionSensorial, Orden
+from tecnicas.controllers import ParticipacionController
+from .init_session_controller import InitSessionController
+
+
+class InitSessionEscalasController(InitSessionController):
+ tester: Catador
+ session: SesionSensorial
+ order: Orden | dict
+
+ def __init__(self, sensorial_session, user_tester):
+ super().__init__(sensorial_session, user_tester)
+ self.current_direction = "tecnicas/forms_tester/init_scales_test.html"
+ self.escalas_direction = "cata_system:session_convencional"
+ self.cata_direction = "cata_system:session_cata"
+
+ def controllGet(self, request: HttpRequest):
+ context = {
+ "session": self.session,
+ "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
+ }
+
+ order = self.checkAndAssignOrder()
+ if isinstance(order, dict):
+ context["error"] = order["error"]
+ return render(request, self.current_direction, context)
+
+ is_end = self.isEndedSession()
+
+ request.session["id_order"] = order.id
+ context["has_ended"] = is_end
+
+ if is_end:
+ context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
+
+ if "error" in request.GET:
+ context["error"] = request.GET["error"]
+
+ return render(request, self.current_direction, context)
+
+ def controllPost(self, request: HttpRequest):
+ context = {
+ "session": self.session,
+ "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
+ }
+
+ if request.POST["action"] == "start_posting":
+ parameters = {
+ "code_sesion": self.session.codigo_sesion
+ }
+
+ is_end = self.isEndedSession()
+ if is_end:
+ context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
+ return render(request, self.current_direction, context)
+
+ update_participation = ParticipacionController.enterSession(
+ tester=request.user.user_catador, session=self.session)
+ if isinstance(update_participation, dict):
+ context["error"] = update_participation["error"]
+ return render(request, self.current_direction, context)
+
+ request.session["id_participation"] = update_participation.id
+
+ if self.session.tecnica.tipo_tecnica.nombre_tecnica == "cata":
+ return redirect(reverse(self.cata_direction, kwargs=parameters))
+
+ return redirect(reverse(self.escalas_direction, kwargs=parameters))
+
+ elif request.POST["action"] == "exit_session":
+ response = ParticipacionController.outSession(
+ tester=request.user.user_catador, session=self.session)
+ if isinstance(response, dict):
+ context["error"] = response["error"]
+ return render(request, self.current_direction, context)
+
+ else:
+ context["error"] = "Acción sin especificar"
+ return render(request, self.current_direction, context)
diff --git a/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_napping_controller.py b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_napping_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..42a12b6b23a70a9c690fece551f4f043bf2d61c3
--- /dev/null
+++ b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_napping_controller.py
@@ -0,0 +1,80 @@
+from django.http import HttpRequest
+from django.shortcuts import render, redirect
+from django.urls import reverse
+from tecnicas.models import Participacion, TecnicaModalidad
+from tecnicas.controllers import ParticipacionController
+from .init_session_controller import InitSessionController
+
+
+class InitSessionNappingController(InitSessionController):
+ def __init__(self, sensorial_session, user_tester):
+ super().__init__(sensorial_session, user_tester)
+ self.current_direction = "tecnicas/forms_tester/init_test_napping.html"
+ self.napping_direction = "cata_system:session_napping"
+
+ def controllGet(self, request: HttpRequest):
+ self.context = {
+ "session": self.session,
+ "type_technique": "napping",
+ "has_ended": self.isEndedSession()
+ }
+
+ self.setStatusSession()
+
+ if "error" in request.GET:
+ self.context["error"] = request.GET["error"]
+
+ return render(request, self.current_direction, self.context)
+
+ def isEndedSession(self):
+ return Participacion.objects.get(
+ tecnica=self.session.tecnica, catador=self.tester).finalizado
+
+ def controllPost(self, request: HttpRequest):
+ context = {
+ "session": self.session,
+ "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
+ }
+
+ use_action = request.POST["action"]
+
+ if use_action == "start_posting":
+ parameters = {
+ "code_sesion": self.session.codigo_sesion
+ }
+
+ is_end = self.isEndedSession()
+ if is_end:
+ context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
+ return render(request, self.current_direction, context)
+
+ update_participation = ParticipacionController.enterSession(
+ tester=request.user.user_catador, session=self.session)
+
+ if isinstance(update_participation, dict):
+ context["error"] = update_participation["error"]
+ return render(request, self.current_direction, context)
+
+ request.session["id_participation"] = update_participation.id
+
+ return redirect(reverse(self.napping_direction, kwargs=parameters))
+
+ elif use_action == "exit_session":
+ response = ParticipacionController.outSession(
+ tester=request.user.user_catador, session=self.session)
+ if isinstance(response, dict):
+ context["error"] = response["error"]
+ return render(request, self.current_direction, context)
+
+ else:
+ context["error"] = "Acción sin especificar"
+ return render(request, self.current_direction, context)
+
+ def setStatusSession(self):
+ technique_mode = TecnicaModalidad.objects.get(
+ tecnica=self.session.tecnica).modalidad.nombre
+
+ if technique_mode == "sin modalidad":
+ self.context["status"] = "La sesión usa Napping"
+ else:
+ self.context["status"] = f"La sesión usa Napping con modalidad {technique_mode}"
diff --git a/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_pf_controller.py b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_pf_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..299272eb21efb751ecec9362a0f0cbe9b7f324a0
--- /dev/null
+++ b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_pf_controller.py
@@ -0,0 +1,144 @@
+from django.http import HttpRequest
+from django.shortcuts import render, redirect
+from django.urls import reverse
+from tecnicas.models import Participacion, Producto, Dato, ListaPalabras
+from tecnicas.controllers import ParticipacionController
+from .init_session_controller import InitSessionController
+
+
+class InitSessionPFController(InitSessionController):
+ def __init__(self, sensorial_session, user_tester):
+ super().__init__(sensorial_session, user_tester)
+ self.current_direction = "tecnicas/forms_tester/init_pf_test.html"
+ self.pf_direction = "cata_system:session_pf"
+
+ def controllGet(self, request: HttpRequest):
+ context = {
+ "session": self.session,
+ "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
+ }
+
+ (is_end, message, rep_show) = self.isEndedSession()
+
+ context["has_ended"] = is_end
+ context["activity"] = message
+ context["repetition"] = rep_show
+
+ if "error" in request.GET:
+ context["error"] = request.GET["error"]
+
+ return render(request, self.current_direction, context)
+
+ def controllPost(self, request: HttpRequest):
+ context = {
+ "session": self.session,
+ "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
+ }
+
+ action = request.POST["action"]
+
+ if action == "start_posting":
+ parameters = {
+ "code_sesion": self.session.codigo_sesion
+ }
+
+ (is_end, message, rep_show) = self.isEndedSession()
+ if is_end:
+ return self.controllGet(request)
+
+ update_participation = ParticipacionController.enterSession(
+ tester=request.user.user_catador, session=self.session)
+ if isinstance(update_participation, dict):
+ context["error"] = update_participation["error"]
+ return render(request, self.current_direction, context)
+
+ request.session["id_participation"] = update_participation.id
+
+ return redirect(reverse(self.pf_direction, kwargs=parameters))
+
+ elif action == "exit_session":
+ response = ParticipacionController.outSession(
+ tester=request.user.user_catador, session=self.session)
+ if isinstance(response, dict):
+ context["error"] = response["error"]
+ return self.controllGet(request)
+
+ else:
+ context["error"] = "Acción sin especificar"
+ return render(request, self.current_direction, context)
+
+ def isEndedSession(self) -> tuple[bool, str, int]:
+ rep = self.session.tecnica.repeticion
+
+ is_end = False
+ message = ""
+ repetitiom_show = 0
+
+ if rep == 1:
+ is_end = self.endedSessionMakeList()
+ message = "Ya has creado la Lista de palabras inicial" if is_end else "Debes crear tu lista de palabras inicial"
+ repetitiom_show = 0
+ elif rep == 2:
+ is_end = self.endedSessionMakeList()
+ message = "Ya has creado la Lista de palabras final" if is_end else "Debes crear tu lista de palabras final"
+ repetitiom_show = 0
+ elif rep >= 3:
+ is_end = self.endedSessionRepetition()
+ message = "Has finalizado con el proceso de calificación" if is_end else "Debe hacer tu proceso de calificación"
+ repetitiom_show = rep - 2
+ else:
+ message = "Parece que la repetición es cero, no es posible hacer algo ahora mismo"
+
+ return (is_end, message, repetitiom_show)
+
+ def endedSessionMakeList(self):
+ try:
+ return Participacion.objects.get(
+ catador=self.tester, tecnica=self.session.tecnica).finalizado
+ except Participacion.DoesNotExist:
+ print("No se ha encontrado la participación")
+ return False
+
+ def endedSessionRepetition(self):
+ try:
+ participation = Participacion.objects.get(
+ catador=self.tester, tecnica=self.session.tecnica)
+ self.session.refresh_from_db()
+
+ # ////////////////////////////////////////////////////////////// #
+ #
+ # numero_datos_esperadas = num_productos * num_palabras
+ # Si numero_datos_esperadas es igual a numero_datos_actuales en la repetcion R
+ # Ha terminado la repeticion
+ #
+ # ////////////////////////////////////////////////////////////// #
+
+ if participation.finalizado:
+ num_products = Producto.objects.filter(
+ id_tecnica=self.session.tecnica).count()
+
+ num_words = ListaPalabras.objects.get(
+ tecnica=self.session.tecnica,
+ catador=self.tester,
+ es_final=True
+ ).palabras.all().count()
+
+ expected_ratings_repetition = num_products * num_words
+
+ technique = self.session.tecnica
+ num_ratings_now = Dato.objects.filter(
+ id_calificacion__id_catador=self.tester,
+ id_calificacion__id_tecnica=technique,
+ id_calificacion__num_repeticion=technique.repeticion
+ ).count()
+
+ is_end = num_ratings_now >= expected_ratings_repetition
+
+ return is_end
+
+ else:
+ return participation.finalizado
+
+ except Participacion.DoesNotExist:
+ print("No se ha encontrado la participación")
+ return False
diff --git a/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_rata_controller.py b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_rata_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..405986dbfbd0a93c1b00c61f0b0d2bd7e1068876
--- /dev/null
+++ b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_rata_controller.py
@@ -0,0 +1,24 @@
+from django.http import HttpRequest
+from django.shortcuts import render
+from .init_session_controller import InitSessionController
+
+
+class InitSessionRATAController(InitSessionController):
+ def __init__(self, sensorial_session, user_tester):
+ super().__init__(sensorial_session, user_tester)
+ self.current_direction = "tecnicas/forms_tester/init_scales_test.html"
+
+ def controllGet(self, request: HttpRequest):
+ context = {
+ "session": self.session,
+ "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
+ }
+
+ is_end = self.isEndedSession()
+
+ context["has_ended"] = is_end
+
+ if is_end:
+ context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
+
+ return render(request, self.current_direction, context)
diff --git a/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_sort_controller.py b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_sort_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..7d21f8c5f23157563b2dfbb826118980ac426b2f
--- /dev/null
+++ b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_sort_controller.py
@@ -0,0 +1,77 @@
+from django.http import HttpRequest
+from django.shortcuts import render, redirect
+from django.urls import reverse
+from tecnicas.models import Participacion
+from tecnicas.controllers import ParticipacionController
+from .init_session_controller import InitSessionController
+
+
+class InitSessionSortController(InitSessionController):
+ def __init__(self, sensorial_session, user_tester):
+ super().__init__(sensorial_session, user_tester)
+ self.current_direction = "tecnicas/forms_tester/init_test_sort.html"
+ self.sort_direction = "cata_system:session_sort"
+
+ def controllGet(self, request: HttpRequest, error=""):
+ context = {
+ "session": self.session,
+ "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
+ }
+
+ is_end = self.isEndedSession()
+
+ context["has_ended"] = is_end
+
+ if is_end:
+ context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
+
+ if "error" in request.GET:
+ context["error"] = request.GET["error"]
+
+ return render(request, self.current_direction, context)
+
+ def isEndedSession(self):
+ participation = Participacion.objects.get(
+ catador=self.tester, tecnica=self.session.tecnica)
+
+ return participation.finalizado
+
+ def controllPost(self, request: HttpRequest):
+ context = {
+ "session": self.session,
+ "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
+ }
+
+ use_action = request.POST["action"]
+
+ if use_action == "start_posting":
+ parameters = {
+ "code_sesion": self.session.codigo_sesion
+ }
+
+ is_end = self.isEndedSession()
+ if is_end:
+ context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
+ return render(request, self.current_direction, context)
+
+ update_participation = ParticipacionController.enterSession(
+ tester=request.user.user_catador, session=self.session)
+
+ if isinstance(update_participation, dict):
+ context["error"] = update_participation["error"]
+ return render(request, self.current_direction, context)
+
+ request.session["id_participation"] = update_participation.id
+
+ return redirect(reverse(self.sort_direction, kwargs=parameters))
+
+ elif use_action == "exit_session":
+ response = ParticipacionController.outSession(
+ tester=request.user.user_catador, session=self.session)
+ if isinstance(response, dict):
+ context["error"] = response["error"]
+ return render(request, self.current_direction, context)
+
+ else:
+ context["error"] = "Acción sin especificar"
+ return render(request, self.current_direction, context)
diff --git a/tecnicas/controllers/views_controller/sessions_tester/login_session_tester_controller.py b/tecnicas/controllers/views_controller/sessions_tester/login_session_tester_controller.py
index 452bf5dd9c524e41223a3f78be590e209ae32f64..beec3493c6d6ac2d7590e752cf06306b3c20f0b6 100644
--- a/tecnicas/controllers/views_controller/sessions_tester/login_session_tester_controller.py
+++ b/tecnicas/controllers/views_controller/sessions_tester/login_session_tester_controller.py
@@ -1,4 +1,4 @@
-from django.http import HttpRequest, JsonResponse
+from django.http import HttpRequest
from django.shortcuts import render, redirect
from django.urls import reverse
from django.db import transaction
@@ -12,6 +12,7 @@ class LoginSessionTesterController():
taster_participation: Participacion
current_direcction = "tecnicas/forms_tester/login_session.html"
destinity_direcction = "cata_system:catador_init_session"
+ context = {}
def __init__(self):
self.tester = Catador()
@@ -28,17 +29,18 @@ class LoginSessionTesterController():
return controller_error("Credenciales inválidas")
def validateEntryEscalas(self, request=HttpRequest):
- context = {}
+ self.context = {}
if not self.session.activo:
- context["error"] = "La sesión no está activa actualmente"
- return render(request, self.current_direcction, context)
+ self.context["error"] = "La sesión no está activa actualmente"
+ return render(request, self.current_direcction, self.context)
if self.session.tecnica.repeticion == 1:
try:
self.taster_participation = Participacion.objects.get(
tecnica=self.session.tecnica, catador=self.tester)
- context["error"] = "Usted ya esta dentro de la sesión"
- return render(request, self.current_direcction, context)
+ self.context["error"] = "Usted ya esta dentro de la sesión"
+ return render(request, self.current_direcction, self.context)
+
except Participacion.DoesNotExist:
with transaction.atomic():
code_session = self.session.codigo_sesion
@@ -50,8 +52,8 @@ class LoginSessionTesterController():
tecnica=self.session.tecnica).count()
if current_num_testers >= max_testers:
- context["error"] = "La sesión ha alcanzado el número máximo de catadores"
- return render(request, self.current_direcction, context)
+ self.context["error"] = "La sesión ha alcanzado el número máximo de catadores"
+ return render(request, self.current_direcction, self.context)
self.taster_participation = Participacion.objects.create(
tecnica=self.session.tecnica,
@@ -63,21 +65,21 @@ class LoginSessionTesterController():
}
return redirect(reverse(self.destinity_direcction, kwargs=params))
else:
- context["error"] = "Ya no es posible ingresar a la sesión"
- return render(request, self.current_direcction, context)
+ self.context["error"] = "Ya no es posible ingresar a la sesión"
+ return render(request, self.current_direcction, self.context)
- def validateEntryRATA(self, request: HttpRequest):
- context = {}
+ def validateEntryRataCata(self, request: HttpRequest):
+ self.context = {}
if not self.session.activo:
- context["error"] = "La sesión no está activa actualmente"
- return render(request, self.current_direcction, context)
+ self.context["error"] = "La sesión no está activa actualmente"
+ return render(request, self.current_direcction, self.context)
if self.session.tecnica.repeticion <= 1:
try:
self.taster_participation = Participacion.objects.get(
tecnica=self.session.tecnica, catador=self.tester)
- context["error"] = "Usted ya esta dentro de la sesión"
- return render(request, self.current_direcction, context)
+ self.context["error"] = "Usted ya esta dentro de la sesión"
+ return render(request, self.current_direcction, self.context)
except Participacion.DoesNotExist:
with transaction.atomic():
code_session = self.session.codigo_sesion
@@ -95,5 +97,67 @@ class LoginSessionTesterController():
}
return redirect(reverse(self.destinity_direcction, kwargs=params))
else:
- context["error"] = "Imposible acceder a esta sesión"
- return render(request, self.current_direcction, context)
+ self.context["error"] = "Imposible acceder a esta sesión"
+ return render(request, self.current_direcction, self.context)
+
+ def validateEntryLimitTesters(self, request: HttpRequest):
+ self.context = {}
+ if not self.session.activo:
+ self.context["error"] = "La sesión no está activa actualmente"
+ return render(request, self.current_direcction, self.context)
+
+ if self.session.tecnica.repeticion == 1:
+ return self.entrySessionLimitTesters(request)
+
+ else:
+ self.context["error"] = "Ya no es posible ingresar a la sesión"
+ return render(request, self.current_direcction, self.context)
+
+ def validateEntryNapping(self, request: HttpRequest):
+ self.context = {}
+ if not self.session.activo:
+ self.context["error"] = "La sesión no está activa actualmente"
+ return render(request, self.current_direcction, self.context)
+
+ if self.session.tecnica.repeticion == 0:
+ return self.entrySessionLimitTesters(request)
+
+ else:
+ self.context["error"] = "Ya no es posible ingresar a la sesión"
+ return render(request, self.current_direcction, self.context)
+
+ def entrySessionLimitTesters(self, request: HttpRequest):
+ try:
+ self.taster_participation = Participacion.objects.get(
+ tecnica=self.session.tecnica, catador=self.tester)
+ self.context["error"] = "Usted ya esta dentro de la sesión"
+ return render(request, self.current_direcction, self.context)
+
+ except Participacion.DoesNotExist:
+ try:
+ with transaction.atomic():
+ code_session = self.session.codigo_sesion
+ self.session = SesionSensorial.objects.select_for_update().get(
+ codigo_sesion=code_session)
+
+ max_testers = self.session.tecnica.limite_catadores
+ current_num_testers = Participacion.objects.filter(
+ tecnica=self.session.tecnica).count()
+
+ if current_num_testers >= max_testers:
+ raise ValueError(
+ "La sesión ha alcanzado el número máximo de catadores")
+
+ self.taster_participation = Participacion.objects.create(
+ tecnica=self.session.tecnica,
+ catador=self.tester,
+ finalizado=False
+ )
+ params = {
+ "code_sesion": self.session.codigo_sesion
+ }
+ return redirect(reverse(self.destinity_direcction, kwargs=params))
+
+ except ValueError as e:
+ self.context["error"] = str(e)
+ return render(request, self.current_direcction, self.context)
diff --git a/tecnicas/controllers/views_controller/sessions_tester/tests_forms/general_test_controller.py b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/general_test_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..ddfc0941a4c6cd166456f1eb4170c232a97d1fbc
--- /dev/null
+++ b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/general_test_controller.py
@@ -0,0 +1,28 @@
+from django.http import HttpRequest
+from django.shortcuts import redirect, render
+from django.urls import reverse
+from tecnicas.models import SesionSensorial, Catador, Participacion
+from tecnicas.controllers import ParticipacionController
+
+
+class GenetalTestController():
+ previus_directory = "cata_system:catador_init_session"
+ context = {}
+ current_directory: str
+
+ def __init__(self, sensorial_session: SesionSensorial, user_tester: Catador):
+ self.tester = user_tester
+ self.session = sensorial_session
+
+ def controllPost(self, request: HttpRequest):
+ action = request.POST["action"]
+
+ if action == "finish_session":
+ self.participation = Participacion.objects.get(
+ tecnica=self.session.tecnica, catador=request.user.user_catador)
+ ParticipacionController.finishSession(self.participation)
+ params = {"code_sesion": self.session.codigo_sesion}
+ return redirect(reverse(self.previus_directory, kwargs=params))
+
+ else:
+ return self.controllGet(request, error="Acción no permitida")
diff --git a/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_cata_controller.py b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_cata_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..13f1badfa70504e81dce9a1819170e24031c311c
--- /dev/null
+++ b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_cata_controller.py
@@ -0,0 +1,55 @@
+from django.http import HttpRequest
+from django.shortcuts import redirect, render
+from django.urls import reverse
+from tecnicas.models import Producto, Participacion, Palabra, Calificacion
+from tecnicas.controllers import PalabrasController, ParticipacionController
+from .general_test_controller import GenetalTestController
+
+
+class TestCataController(GenetalTestController):
+ def __init__(self, sensorial_session, user_tester):
+ super().__init__(sensorial_session, user_tester)
+ self.current_directory = "tecnicas/forms_tester/test_cata.html"
+
+ def controllGet(self, request: HttpRequest):
+ technique = self.session.tecnica
+ self.participation = Participacion.objects.get(
+ tecnica=technique, catador=request.user.user_catador)
+
+ self.context["session"] = self.session
+
+ products_in_technique = Producto.objects.filter(id_tecnica=technique)
+
+ words = PalabrasController.getWordsInTechnique(technique=technique)
+
+ use_product: Producto = None
+ use_words: list[Palabra] = None
+
+ # Revisamos el producto que le falten calificaciones
+ for current_product in products_in_technique:
+ try:
+ rating = Calificacion.objects.get(
+ num_repeticion=technique.repeticion,
+ id_producto=current_product,
+ id_tecnica=technique,
+ id_catador=self.tester
+ )
+ except Calificacion.DoesNotExist:
+ # Si no hay calificacion mandamos el producto actual y todas la palabras
+ use_product = current_product
+ use_words = words
+ break
+
+ # Si no hay producto que falta por calificar finalizar sesion para el Catador
+ if not use_product:
+ updated_participation = ParticipacionController.finishSession(
+ self.participation)
+ params = {
+ "code_sesion": self.session.codigo_sesion
+ }
+ return redirect(reverse(self.previus_directory, kwargs=params))
+
+ self.context["product"] = use_product
+ self.context["words"] = use_words
+
+ return render(request, self.current_directory, self.context)
diff --git a/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_napping_controller.py b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_napping_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..80d6d2d5382f4b5544f2eb742f68ad2be96a4bd2
--- /dev/null
+++ b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_napping_controller.py
@@ -0,0 +1,242 @@
+from django.http import HttpRequest
+from django.shortcuts import redirect, render
+from django.urls import reverse
+from django.db.models import F
+from tecnicas.models import Participacion, Producto, TecnicaModalidad, DatoPunto, Calificacion, Modalidad, Palabra, GrupoProducto
+from tecnicas.forms import ListWordsForm
+from tecnicas.utils import noValidTechnique
+from tecnicas.controllers import ParticipacionController
+from .general_test_controller import GenetalTestController
+
+
+class TestNappingController(GenetalTestController):
+ def __init__(self, sensorial_session, user_tester):
+ super().__init__(sensorial_session, user_tester)
+ self.napping_test = "tecnicas/forms_tester/test_napping.html"
+ self.napping_puf_test = "tecnicas/forms_tester/test_napping_puf.html"
+ self.sort_direction = "tecnicas/forms_tester/test_napping_sort.html"
+
+ def controllGet(self, request: HttpRequest):
+ technique = self.session.tecnica
+
+ self.participation = Participacion.objects.get(
+ tecnica=technique, catador=request.user.user_catador)
+
+ # Comprobar que el Catador no haya finalizado
+ if self.participation.finalizado:
+ params = {
+ "code_sesion": self.session.codigo_sesion
+ }
+ return redirect(reverse(self.previus_directory, kwargs=params))
+
+ name_mode_activate = TecnicaModalidad.objects.get(
+ tecnica=technique).modalidad.nombre
+
+ if name_mode_activate == "sin modalidad":
+ self.context["mode"] = "sin modalidad"
+ return self.nappingTest(request)
+
+ if name_mode_activate == "perfil ultra flash":
+ self.context["mode"] = "perfil ultra flash"
+ return self.nappingPufTest(request)
+
+ if name_mode_activate == "sorting":
+ self.context["mode"] = "sorting"
+ return self.nappingSort(request)
+
+ else:
+ return noValidTechnique(
+ name_view=self.previus_directory,
+ query_params={
+ "error": f"Trabajando en la modalidad: {name_mode_activate}"
+ },
+ params={
+ "code_sesion": self.session.codigo_sesion
+ }
+ )
+
+ def nappingTest(self, request: HttpRequest):
+ self.context["session"] = self.session
+ technique = self.session.tecnica
+
+ products_in_technique = Producto.objects.filter(id_tecnica=technique)
+ self.context["products"] = products_in_technique
+
+ self.setCoordinates()
+
+ return render(request, self.napping_test, self.context)
+
+ def nappingPufTest(self, request: HttpRequest):
+ maked_previus_napping = TecnicaModalidad.objects.get(
+ tecnica=self.session.tecnica)
+
+ self.context["maked_napping"] = True if maked_previus_napping else False
+ self.context["mode"] = "perfil ultra flash"
+ self.context["form"] = ListWordsForm()
+
+ self.context["session"] = self.session
+ technique = self.session.tecnica
+ products_in_technique = Producto.objects.filter(id_tecnica=technique)
+ self.context["products"] = products_in_technique
+ self.setCoordinates()
+ self.setWords()
+
+ return render(request, self.napping_puf_test, self.context)
+
+ def nappingSort(self, request: HttpRequest):
+ self.context["session"] = self.session
+ technique = self.session.tecnica
+
+ products_in_technique = Producto.objects.filter(id_tecnica=technique)
+ self.context["products"] = products_in_technique
+
+ self.context["form"] = ListWordsForm()
+ self.setCoordinates()
+ self.setGroups()
+
+ return render(request, self.sort_direction, self.context)
+
+ def setCoordinates(self):
+ technique = self.session.tecnica
+
+ ratings = Calificacion.objects.filter(
+ num_repeticion=0,
+ id_tecnica=technique,
+ id_catador=self.participation.catador
+ )
+
+ data_points = DatoPunto.objects.filter(
+ calificacion__in=ratings
+ ).values(
+ code=F("calificacion__id_producto__codigoProducto"),
+ px=F("x"),
+ py=F("y"),
+ id_product=F("calificacion__id_producto__id")
+ )
+
+ self.context["data_points"] = list(data_points)
+
+ def setGroups(self):
+ technique = self.session.tecnica
+
+ # Get all product groups for this tester
+ grupos_producto = GrupoProducto.objects.filter(
+ tecnica=technique,
+ catador=self.participation.catador
+ ).prefetch_related('productos', 'palabras')
+
+ groups = []
+ for group in grupos_producto:
+ # Get products in this group
+ products_list = []
+ for product in group.productos.all():
+ products_list.append({
+ 'id': product.id,
+ 'codigoProducto': product.codigoProducto
+ })
+
+ # Get words for this group
+ words_list = list(group.palabras.values_list(
+ 'nombre_palabra', flat=True))
+
+ groups.append({
+ 'id': group.id,
+ 'products': products_list,
+ 'words': words_list
+ })
+
+ self.context["groups"] = groups
+
+ def setWords(self):
+ technique = self.session.tecnica
+
+ ratings = Calificacion.objects.filter(
+ num_repeticion=0,
+ id_tecnica=technique,
+ id_catador=self.participation.catador
+ ).prefetch_related('palabras', 'id_producto')
+
+ words_by_product = {}
+ for rating in ratings:
+ product_code = rating.id_producto.codigoProducto
+ words_list = list(rating.palabras.values_list(
+ 'nombre_palabra', flat=True))
+ if words_list:
+ words_by_product[product_code] = words_list
+
+ self.context["words_by_product"] = words_by_product
+
+ def controllPost(self, request: HttpRequest):
+ action = request.POST.get("action")
+
+ if action == "finish_session":
+ # Get technique and mode
+ technique = self.session.tecnica
+ self.participation = Participacion.objects.get(
+ tecnica=technique, catador=request.user.user_catador)
+
+ name_mode_activate = TecnicaModalidad.objects.get(
+ tecnica=technique).modalidad.nombre
+
+ # Validate based on mode
+ validation_error = self.validateSessionCompletion(
+ technique, name_mode_activate)
+
+ if validation_error:
+ # Return to the appropriate template with error
+ if name_mode_activate == "sin modalidad":
+ return self.nappingTest(request)
+ elif name_mode_activate == "perfil ultra flash":
+ return self.nappingPufTest(request)
+
+ # If validation passes, finish the session
+ ParticipacionController.finishSession(self.participation)
+ params = {"code_sesion": self.session.codigo_sesion}
+ return redirect(reverse(self.previus_directory, kwargs=params))
+
+ # For other actions, call parent's controllPost
+ return super().controllPost(request)
+
+ def validateSessionCompletion(self, technique, mode_name):
+ # Get all products in technique
+ products = Producto.objects.filter(id_tecnica=technique)
+ product_count = products.count()
+
+ # Get all ratings for this tester
+ ratings = Calificacion.objects.filter(
+ num_repeticion=0,
+ id_tecnica=technique,
+ id_catador=self.participation.catador
+ ).select_related('id_producto').prefetch_related('palabras')
+
+ # Check if all products have ratings
+ if ratings.count() != product_count:
+ missing_count = product_count - ratings.count()
+ return f"Faltan {missing_count} producto(s) por evaluar."
+
+ # Check if all ratings have DatoPunto (coordinates)
+ ratings_with_points = DatoPunto.objects.filter(
+ calificacion__in=ratings
+ ).values_list('calificacion_id', flat=True)
+
+ ratings_without_points = ratings.exclude(id__in=ratings_with_points)
+ if ratings_without_points.exists():
+ missing_products = [
+ r.id_producto.codigoProducto for r in ratings_without_points
+ ]
+ return f"Los siguientes productos no tienen coordenadas: {', '.join(missing_products)}"
+
+ # Additional validation for "perfil ultra flash" mode
+ if mode_name == "perfil ultra flash":
+ # Check that each rating has at least one word
+ ratings_without_words = []
+ for rating in ratings:
+ if rating.palabras.count() < 1:
+ ratings_without_words.append(
+ rating.id_producto.codigoProducto)
+
+ if ratings_without_words:
+ return f"Los siguientes productos deben tener al menos 1 palabra: {', '.join(ratings_without_words)}"
+
+ # All validations passed
+ return None
diff --git a/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_pf_controller.py b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_pf_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..205bf4abad4e136c0ce90ea15a481294b642cb72
--- /dev/null
+++ b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_pf_controller.py
@@ -0,0 +1,174 @@
+from django.http import HttpRequest
+from django.shortcuts import redirect, render
+from django.urls import reverse
+from tecnicas.models import Producto, Participacion, Palabra, Calificacion, ListaPalabras, Dato
+from tecnicas.controllers import ParticipacionController, PalabrasController, EscalaController
+from tecnicas.forms import ListWordsForm
+from .general_test_controller import GenetalTestController
+
+
+class TestPFController(GenetalTestController):
+ skip_phases = 2
+
+ def __init__(self, sensorial_session, user_tester):
+ super().__init__(sensorial_session, user_tester)
+
+ def controllGet(self, request: HttpRequest, error=""):
+ self.participation = Participacion.objects.get(
+ tecnica=self.session.tecnica, catador=request.user.user_catador)
+ self.context["session"] = self.session
+
+ if error:
+ self.context["error"] = error
+
+ rep = self.session.tecnica.repeticion
+
+ if rep == 1:
+ self.current_directory = "tecnicas/forms_tester/test_pf_list_words.html"
+ response = self.getFirstPhase(request)
+ elif rep == 2:
+ self.current_directory = "tecnicas/forms_tester/test_pf_list_words.html"
+ response = self.getSecondPhase(request)
+ elif rep >= 3:
+ self.current_directory = "tecnicas/forms_tester/test_pf_rating_list.html"
+ response = self.getRepetitionPhase(request)
+ else:
+ response = self.getErrorRepetition(request)
+
+ return response
+
+ def controllPost(self, request: HttpRequest):
+ action = request.POST["action"]
+
+ if action == "finish_session":
+ self.participation = Participacion.objects.get(
+ tecnica=self.session.tecnica, catador=request.user.user_catador)
+ ParticipacionController.finishSession(self.participation)
+ params = {"code_sesion": self.session.codigo_sesion}
+ return redirect(reverse(self.previus_directory, kwargs=params))
+
+ else:
+ return self.controllGet(request, error="Acción no permitida")
+
+ def getFirstPhase(self, request: HttpRequest):
+ self.participation.refresh_from_db()
+
+ if self.participation.finalizado:
+ params = {
+ "code_sesion": self.session.codigo_sesion
+ }
+ return redirect(reverse(self.previus_directory, kwargs=params))
+
+ self.context["form"] = ListWordsForm()
+ self.context["initial_phase"] = True
+
+ try:
+ tester_list = ListaPalabras.objects.get(
+ tecnica=self.session.tecnica,
+ catador=request.user.user_catador,
+ es_final=False
+ )
+ list_words = list(tester_list.palabras.all())
+ self.context["words"] = list_words
+ except ListaPalabras.DoesNotExist:
+ self.context["words"] = []
+
+ return render(request, self.current_directory, self.context)
+
+ def getSecondPhase(self, request: HttpRequest):
+ self.participation.refresh_from_db()
+
+ if self.participation.finalizado:
+ params = {
+ "code_sesion": self.session.codigo_sesion
+ }
+ return redirect(reverse(self.previus_directory, kwargs=params))
+
+ try:
+ tester_list = ListaPalabras.objects.get(
+ tecnica=self.session.tecnica,
+ catador=request.user.user_catador,
+ es_final=True
+ )
+ except ListaPalabras.DoesNotExist:
+ tester_list = ListaPalabras.objects.get(
+ tecnica=self.session.tecnica,
+ catador=request.user.user_catador,
+ es_final=False
+ )
+
+ list_words = list(tester_list.palabras.all())
+
+ self.context["form"] = ListWordsForm()
+ self.context["initial_phase"] = False
+ self.context["words"] = list_words
+
+ return render(request, self.current_directory, self.context)
+
+ def getRepetitionPhase(self, request: HttpRequest):
+ '''
+ - Obtener todos los productos que se evaluan en la tecnica
+ - Obtener todas las palabras de la lista de palabras del catador
+ - Para cada palabra, comprobar que el numero de Dato sea igual al numero de Productos
+ - Si no hay datos mandar esa palabra por contexto con todos los productos
+ - De las palabras que falten tomar la primera y mandarla en el contexto
+ - Mandar todos los productos por el contexto
+ Nota: Para esta fase no hay necesidad de mandar una escala, la escala se creara en el cliente
+ '''
+ self.participation.refresh_from_db()
+
+ if self.participation.finalizado:
+ params = {"code_sesion": self.session.codigo_sesion}
+ return redirect(reverse(self.previus_directory, kwargs=params))
+
+ technique = self.session.tecnica
+
+ # Obtener todos los productos que se evaluan en la técnica
+ products_in_technique = Producto.objects.filter(id_tecnica=technique)
+
+ # Obtener todas las palabras de la lista del catador (preferir lista final)
+ try:
+ words = list(ListaPalabras.objects.get(
+ tecnica=self.session.tecnica,
+ catador=request.user.user_catador,
+ es_final=True
+ ).palabras.all())
+ except ListaPalabras.DoesNotExist:
+ words = []
+
+ use_word: Palabra = None
+
+ # Revisar que palabra no ha sido calificada en todos los productos
+ for word in words:
+ current_num_data = Dato.objects.filter(
+ id_calificacion__num_repeticion=technique.repeticion,
+ id_calificacion__id_tecnica=technique,
+ id_calificacion__id_catador=request.user.user_catador,
+ id_palabra=word
+ ).count()
+
+ if not current_num_data:
+ use_word = word
+ break
+ elif current_num_data < len(products_in_technique):
+ self.context["error"] = "Se ha detectado una inconsistencia en los datos que se deben calificar, algunos productos no han sido calificados"
+ return render(request, self.current_directory, self.context)
+
+ if not use_word:
+ self.participation = Participacion.objects.get(
+ tecnica=self.session.tecnica, catador=request.user.user_catador)
+ ParticipacionController.finishSession(self.participation)
+ params = {"code_sesion": self.session.codigo_sesion}
+ return redirect(reverse(self.previus_directory, kwargs=params))
+
+ self.context["word"] = use_word
+ self.context["products"] = products_in_technique
+ self.context["repetition"] = technique.repeticion - self.skip_phases
+
+ return render(request, self.current_directory, self.context)
+
+ def getErrorRepetition(self, request: HttpRequest):
+ params = {
+ "code_sesion": self.session.codigo_sesion
+ }
+ return redirect(reverse(self.previus_directory, kwargs=params))
diff --git a/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_rata_controller.py b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_rata_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..61cfea2fe30e62cdb57ef3c2b7c3e12374f38ffd
--- /dev/null
+++ b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_rata_controller.py
@@ -0,0 +1,87 @@
+from django.http import HttpRequest
+from django.shortcuts import redirect, render
+from django.urls import reverse
+from tecnicas.models import Participacion, Producto, Calificacion, Palabra
+from tecnicas.controllers import ParticipacionController, PalabrasController, EscalaController
+from .general_test_controller import GenetalTestController
+
+
+class TestRataController(GenetalTestController):
+ def __init__(self, sensorial_session, user_tester):
+ super().__init__(sensorial_session, user_tester)
+ self.current_directory = "tecnicas/forms_tester/test_convencional.html"
+
+ def controllGet(self, request: HttpRequest):
+ technique = self.session.tecnica
+ self.participation = Participacion.objects.get(
+ tecnica=technique, catador=request.user.user_catador)
+
+ self.context["session"] = self.session
+
+ products_in_technique = Producto.objects.filter(id_tecnica=technique)
+
+ words = PalabrasController.getWordsInTechnique(technique=technique)
+
+ use_product: Producto = None
+ use_words: list[Palabra] = None
+
+ # Revisamos el producto que le falten calificaciones
+ for current_product in products_in_technique:
+ try:
+ rating = Calificacion.objects.get(
+ num_repeticion=technique.repeticion,
+ id_producto=current_product,
+ id_tecnica=technique,
+ id_catador=self.tester
+ )
+ except Calificacion.DoesNotExist:
+ # Si no hay calificacion mandamos el producto actual y todas la palabras
+ use_product = current_product
+ use_words = words
+ break
+
+ # Obtener los datos asociados para la calificacion para ver que palabras quedan por calificar
+ recoreded_data = rating.dato_calificacion.all()
+
+ if not recoreded_data:
+ # Si no hay datos entonces devolver el producto con todas las palabras
+ use_product = current_product
+ use_words = words
+ break
+ else:
+ words_to_use = PalabrasController.getWordsWithoutData(
+ recoreded_data=recoreded_data, words=words)
+
+ # Si quedan palabras por calificar mandar las palabras con el producto
+ if not isinstance(words_to_use, dict) and words_to_use:
+ use_product = current_product
+ use_words = words_to_use
+ break
+
+ # Si no hay producto que falta por calificar finalizar sesion para el Catador
+ if not use_product:
+ updated_participation = ParticipacionController.finishSession(
+ self.participation)
+ params = {
+ "code_sesion": self.session.codigo_sesion
+ }
+ return redirect(reverse(self.previus_directory, kwargs=params))
+
+ self.context["product"] = use_product
+ self.context["words"] = use_words
+
+ # Agregar informacion de la escala
+ scale = EscalaController.getScaleByTechnique(technique=technique)
+ self.context["scale"] = scale
+ self.context["type_scale"] = scale.id_tipo_escala.nombre_escala
+
+ use_tags = EscalaController.getRelatedTagsInScale(scale=scale)
+ self.context["tags"] = use_tags
+
+ if self.context["type_scale"] == "continua":
+ self.context["size_scale"] = {
+ "max_size": scale.longitud * 100,
+ "middle_size": (scale.longitud * 100)/2
+ }
+
+ return render(request, self.current_directory, self.context)
diff --git a/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_scales_controller.py b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_scales_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..19985ddd5d666bc206c8f9e2d11f6226d96a2800
--- /dev/null
+++ b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_scales_controller.py
@@ -0,0 +1,87 @@
+from django.http import HttpRequest
+from django.shortcuts import redirect, render
+from django.urls import reverse
+from tecnicas.models import SesionSensorial, Catador, Participacion, Producto, Calificacion, Palabra
+from tecnicas.controllers import PosicionController, CalificacionController, ParticipacionController, PalabrasController, EscalaController
+from .general_test_controller import GenetalTestController
+
+
+class TestScalesController(GenetalTestController):
+ def __init__(self, sensorial_session, user_tester):
+ super().__init__(sensorial_session, user_tester)
+ self.current_directory = "tecnicas/forms_tester/test_convencional.html"
+
+ def controllGet(self, request: HttpRequest):
+ technique = self.session.tecnica
+ self.participation = Participacion.objects.get(
+ tecnica=technique, catador=request.user.user_catador)
+
+ ctx = self.context
+ ctx["session"] = self.session
+
+ # Obtener posiciones y palabras de la técnica
+ positions_in_order = PosicionController.getPostionsInOrder(
+ id_order=request.session["id_order"])
+ aligned_positions_in_order = sorted(
+ positions_in_order, key=lambda p: p.posicion)
+ words = PalabrasController.getWordsInTechnique(technique=technique)
+
+ # Comprobar siguiente posición sin calificar
+ (next_position, end_products) = CalificacionController.checkPositionWithoutRating(
+ positions=aligned_positions_in_order,
+ user_cata=request.user.user_catador,
+ repetition=technique.repeticion,
+ technique=technique,
+ num_words=len(words)
+ )
+
+ # Si no hay productos se finaliza la sesion
+ if end_products:
+ ParticipacionController.finishSession(self.participation)
+ params = {"code_sesion": self.session.codigo_sesion}
+ return redirect(reverse('cata_system:catador_init_session', kwargs=params))
+
+ # Si devuelve una lista, tomar el primer elemento
+ if isinstance(next_position, list):
+ next_position = next_position[0]
+
+ # Producto a calificar ahora
+ product = next_position.id_producto
+ ctx["product"] = product
+
+ # Revisar las palabras para calificar
+ try:
+ rating = Calificacion.objects.get(
+ num_repeticion=technique.repeticion,
+ id_producto=product,
+ id_tecnica=technique,
+ id_catador=self.tester
+ )
+ there_rating = True
+ except Calificacion.DoesNotExist:
+ there_rating = False
+
+ # Si no hay calificaciones previas, usar todas las palabras
+ if not there_rating:
+ ctx["words"] = words
+ else:
+ ratings_product = rating.dato_calificacion.all()
+ # Filtrar palabras que faltan
+ words_to_use = PalabrasController.getWordsWithoutData(
+ recoreded_data=ratings_product,
+ words=words
+ )
+ ctx["words"] = words_to_use
+
+ # Escala y etiquetas relacionadas
+ scale = EscalaController.getScaleByTechnique(technique=technique)
+ ctx["scale"] = scale
+ ctx["type_scale"] = scale.id_tipo_escala.nombre_escala
+ ctx["tags"] = EscalaController.getRelatedTagsInScale(scale=scale)
+ if ctx["type_scale"] == "continua":
+ ctx["size_scale"] = {
+ "max_size": scale.longitud * 100,
+ "middle_size": (scale.longitud * 100)/2
+ }
+
+ return render(request, self.current_directory, ctx)
diff --git a/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_sort_controller.py b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_sort_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6095a7b4c7e6adaccf7a677db9e62908cf80719
--- /dev/null
+++ b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_sort_controller.py
@@ -0,0 +1,49 @@
+from django.http import HttpRequest
+from django.shortcuts import redirect, render
+from django.urls import reverse
+from tecnicas.models import Participacion, Producto, Calificacion, Palabra, GrupoProducto
+from tecnicas.controllers import ParticipacionController, PalabrasController, EscalaController
+from tecnicas.forms import ListWordsForm
+from .general_test_controller import GenetalTestController
+
+
+class TestSortController(GenetalTestController):
+ def __init__(self, sensorial_session, user_tester):
+ super().__init__(sensorial_session, user_tester)
+ self.current_directory = "tecnicas/forms_tester/test_sort.html"
+
+ def controllGet(self, request: HttpRequest):
+ '''
+ Objetivo: Entregar al cliente los grupos de productos guardados hechos por el catador en una lista, de lo contrario solo mandar una lista vacia
+ - Comprobar que el Catador aun no finalice su la sesion
+ - Obtener todos los productos en la tecnica
+ - Obtener todos los grupos creadas por el usuario
+ - Si hay grupos, cada item de la lista a mandar debe incluir los productos asociados al grupo como las palabras que describen al grupo
+ - Si no hay grupos, solo mandar una lista vacia
+ - Mandar la lista de grupos como la lista de productos
+ - Agregar el formulario para describir los grupo
+ '''
+ self.context["session"] = self.session
+ technique = self.session.tecnica
+
+ self.participation = Participacion.objects.get(
+ tecnica=technique, catador=request.user.user_catador)
+
+ # Comprobar que el Catador no haya finalizado
+ if self.participation.finalizado:
+ params = {
+ "code_sesion": self.session.codigo_sesion
+ }
+ return redirect(reverse(self.previus_directory, kwargs=params))
+
+ products_in_technique = Producto.objects.filter(id_tecnica=technique)
+ self.context["products"] = products_in_technique
+
+ grups_products = GrupoProducto.objects.filter(
+ tecnica=technique, catador=request.user.user_catador)
+
+ self.context["grups_products"] = grups_products or []
+
+ self.context["form_word"] = ListWordsForm()
+
+ return render(request, self.current_directory, self.context)
diff --git a/tecnicas/controllers/views_controller/vocabulary_manage/create_vocabulary_controller.py b/tecnicas/controllers/views_controller/vocabulary_manage/create_vocabulary_controller.py
index bf86d76cac1508fcecb9a9933aee004a768f864a..15af699b6a7dbf1efcea7a8657294bf7b3fb04e4 100644
--- a/tecnicas/controllers/views_controller/vocabulary_manage/create_vocabulary_controller.py
+++ b/tecnicas/controllers/views_controller/vocabulary_manage/create_vocabulary_controller.py
@@ -19,13 +19,6 @@ class CreateVocabularyController():
"form_word": WordForm(),
"words": []
}
-
- if "name_voca" in request.GET:
- current = Vocabulario.objects.get(
- nombre_vocabulario=request.GET["name_voca"])
- self.context["name_vacabulary"] = current.nombre_vocabulario
- self.context["words"] = current.palabras.all()
-
return render(request, self.current_url, self.context)
def controllPost(self, request: HttpRequest):
@@ -39,72 +32,28 @@ class CreateVocabularyController():
return render(request, self.current_url, self.context)
new_vocabulary_name = request.POST.get("nombre_vocabulario").strip()
- is_update = request.POST.get("is_update")
-
- print(is_update)
-
- if is_update:
- if "original_name" not in request.POST:
- self.context["error"] = "Nombre original de vocabulario requerido"
- return render(request, self.current_url, self.context)
- original_name = request.POST["original_name"].strip()
-
- if original_name != new_vocabulary_name:
- if Vocabulario.objects.filter(nombre_vocabulario=new_vocabulary_name).exists():
- self.context["error"] = "Ya existe un vocabulario con el nombre pasado"
- return render(request, self.current_url, self.context)
+ try:
+ new_vocababulary = Vocabulario.objects.create(
+ nombre_vocabulario=new_vocabulary_name)
+ except IntegrityError:
+ self.context["error"] = "Ya existe un vocabulario con ese nombre"
+ return render(request, self.current_url, self.context)
+ words_json = request.POST.get("words", "")
+ if words_json:
try:
- current_vocababulary = Vocabulario.objects.get(
- nombre_vocabulario=original_name)
- except Vocabulario.DoesNotExist:
- self.context["error"] = "No existe un vocabulario con ese nombre"
- return render(request, self.current_url, self.context)
-
- words_json = request.POST.get("words", "")
- if words_json:
- try:
- words_list = json.loads(words_json)
+ words_list = json.loads(words_json)
- ids = [int(w.get("id", 0))
- for w in words_list if str(w.get("id", "")).isdigit()]
+ ids = [int(w.get("id", 0))
+ for w in words_list if str(w.get("id", "")).isdigit()]
- words = Palabra.objects.filter(id__in=ids)
+ words = Palabra.objects.filter(id__in=ids)
- current_vocababulary.palabras.set(words)
- current_vocababulary.nombre_vocabulario = new_vocabulary_name
- current_vocababulary.save()
- except (json.JSONDecodeError, ValueError):
- self.context["error"] = 'Ocurrió un error al revisar las palabras, revise “Ver vocabularios”, para reasignar las palabras'
- return render(request, self.current_url, self.context)
-
- self.context["message"] = 'Vocabulario creado con éxito, puedes revisarlo en "Ver vocabularios"'
- return render(request, self.current_url, self.context)
- elif not is_update:
- try:
- new_vocababulary = Vocabulario.objects.create(
- nombre_vocabulario=new_vocabulary_name)
- except IntegrityError:
- self.context["error"] = "Ya existe un vocabulario con ese nombre"
+ new_vocababulary.palabras.add(*words)
+ except (json.JSONDecodeError, ValueError):
+ self.context["error"] = 'Ocurrió un error al revisar las palabras, revise “Ver vocabularios”, para reasignar las palabras'
return render(request, self.current_url, self.context)
- words_json = request.POST.get("words", "")
- if words_json:
- try:
- words_list = json.loads(words_json)
-
- ids = [int(w.get("id", 0))
- for w in words_list if str(w.get("id", "")).isdigit()]
-
- words = Palabra.objects.filter(id__in=ids)
-
- new_vocababulary.palabras.add(*words)
- except (json.JSONDecodeError, ValueError):
- self.context["error"] = 'Ocurrió un error al revisar las palabras, revise “Ver vocabularios”, para reasignar las palabras'
- return render(request, self.current_url, self.context)
-
- self.context["message"] = 'Vocabulario actualziado con éxito, puedes revisarlo en "Ver vocabularios"'
- return render(request, self.current_url, self.context)
- else:
- pass
+ self.context["message"] = 'Vocabulario creado con éxito, puedes revisarlo en "Ver vocabularios"'
+ return render(request, self.current_url, self.context)
diff --git a/tecnicas/controllers/views_controller/vocabulary_manage/list_vocabulary_controller.py b/tecnicas/controllers/views_controller/vocabulary_manage/list_vocabulary_controller.py
index 6b8daeaa11b4fa7abfa5259a2182a0c015f189c4..985b27972e0f33e5d72b47d4d394e2e06aa4bde0 100644
--- a/tecnicas/controllers/views_controller/vocabulary_manage/list_vocabulary_controller.py
+++ b/tecnicas/controllers/views_controller/vocabulary_manage/list_vocabulary_controller.py
@@ -40,7 +40,7 @@ class ListVocabularyController():
return controller_error("índice inválido")
if not vocabularies_in_page.object_list:
- return controller_error("Sin registros de Participaciones")
+ return controller_error("Sin registros de Vocabularios")
current_page = vocabularies_in_page.number
is_last_page = not current_page < paginator.num_pages
diff --git a/tecnicas/controllers/views_controller/vocabulary_manage/view_vocabulary_controller.py b/tecnicas/controllers/views_controller/vocabulary_manage/view_vocabulary_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..100d6bb35bed9695530a130ccdfdbb029451b251
--- /dev/null
+++ b/tecnicas/controllers/views_controller/vocabulary_manage/view_vocabulary_controller.py
@@ -0,0 +1,28 @@
+from django.shortcuts import render
+from django.http import HttpRequest
+from tecnicas.models import Vocabulario
+
+
+class ViewVocabularyController():
+ context = {}
+ current_url = "tecnicas/manage_vocabulary/view-vocabulary.html"
+
+ def __init__(self):
+ self.context = {}
+
+ def controllGet(self, request: HttpRequest, nombre_vocabulario: str):
+ try:
+ vocabulary = Vocabulario.objects.get(
+ nombre_vocabulario=nombre_vocabulario)
+
+ self.context = {
+ "vocabulary": vocabulary,
+ "words": vocabulary.palabras.all().order_by('nombre_palabra')
+ }
+
+ except Vocabulario.DoesNotExist:
+ self.context = {
+ "error": f"No existe un vocabulario con el nombre '{nombre_vocabulario}'"
+ }
+
+ return render(request, self.current_url, self.context)
diff --git a/tecnicas/forms/__init__.py b/tecnicas/forms/__init__.py
index 75282f742f8f495728d7adf450e5403b5fb073a3..1add46c25cd8a274807116652797d50b80795081 100644
--- a/tecnicas/forms/__init__.py
+++ b/tecnicas/forms/__init__.py
@@ -1,9 +1,14 @@
from .create_session.sesion_basic_form import SesionBasicForm
from .create_session.sesiob_basic_cata_form import SesionBasicCATAForm
+from .create_session.sesion_basic_pf_form import SesionBasicPFForm
from .create_session.sesion_tags_form import SesionTagsForm
+from .create_session.sesion_basic_sort_form import SesionBasicSortForm
+from .create_session.sesion_basic_napping import SesionBasicNappingForm
from .etiqueta_form import EtiquetaForm
from .codes_form import CodesForm
from .catador_form import CatadorForm
from .word_form import WordForm
from .vocabulary_select import VocabularioSelectForm
+
+from .list_words_form import ListWordsForm
diff --git a/tecnicas/forms/catador_form.py b/tecnicas/forms/catador_form.py
index 465abd0de2e2ee7c2a3d976a8cff028dfde3340f..55010c735817656e3cb635af332029ef7a706fa0 100644
--- a/tecnicas/forms/catador_form.py
+++ b/tecnicas/forms/catador_form.py
@@ -138,8 +138,6 @@ class CatadorForm(forms.Form):
username = cleaned_data.get("nombre_usuario")
is_update = cleaned_data.get("is_update")
- print(username, is_update)
-
if not is_update:
if User.objects.filter(username__iexact=username).exists():
raise forms.ValidationError(
diff --git a/tecnicas/forms/codes_form.py b/tecnicas/forms/codes_form.py
index 5d3a1c0998a7d4c13a856fb2770945f8be2067c5..15b723d6b6e1beb2d83ab9a02d687141acba68d7 100644
--- a/tecnicas/forms/codes_form.py
+++ b/tecnicas/forms/codes_form.py
@@ -9,3 +9,11 @@ class CodesForm(forms.Form):
self.fields[f'producto_{index+1}'] = forms.CharField(max_length=3, required=True, min_length=3, initial=code, label=f"codigo {index+1}", widget=forms.TextInput(attrs={
"class": "ct-code bg-surface-ligt p-1 border-b-1 text-center w-full disabled:bg-surface-general uppercase"
}))
+
+ def clean(self):
+ cleaned_data = super().clean()
+ # Convert all product codes to uppercase
+ for field_name, value in cleaned_data.items():
+ if field_name.startswith('producto_') and value:
+ cleaned_data[field_name] = value.upper()
+ return cleaned_data
diff --git a/tecnicas/forms/create_session/sesiob_basic_cata_form.py b/tecnicas/forms/create_session/sesiob_basic_cata_form.py
index b578857573b1257f2dc2dc27b36d488def2f559a..4a58d9be5dcbbe55aff7bda0b2f684cce657f293 100644
--- a/tecnicas/forms/create_session/sesiob_basic_cata_form.py
+++ b/tecnicas/forms/create_session/sesiob_basic_cata_form.py
@@ -22,6 +22,11 @@ class SesionBasicCATAForm(forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self.fields['estilo_palabras'] = forms.ModelChoiceField(queryset=EstiloPalabra.objects.all(), widget=forms.RadioSelect(attrs={
+ options = [
+ ("atributos", "atributos"),
+ ("vocabulario", "vocabulario")
+ ]
+
+ self.fields['estilo_palabras'] = forms.ChoiceField(choices=options, widget=forms.RadioSelect(attrs={
"class": "uppercase text-lg tracking-wider font-medium p-2 px-4 active:px-5 transition-all rounded-xl bg-blue-500 text-white",
- }), required=True, initial=EstiloPalabra.objects.first())
+ }), required=True, initial=options[0])
diff --git a/tecnicas/forms/create_session/sesion_basic_form.py b/tecnicas/forms/create_session/sesion_basic_form.py
index 34fef6e39575562583ac21e8e470be0eeb7874af..1fd90dcac0ce54d36a342d3af94080f08aa5da47 100644
--- a/tecnicas/forms/create_session/sesion_basic_form.py
+++ b/tecnicas/forms/create_session/sesion_basic_form.py
@@ -38,9 +38,14 @@ class SesionBasicForm(forms.Form):
if initial_conf is None:
initial_conf = {}
- self.fields['estilo_palabras'] = forms.ModelChoiceField(queryset=EstiloPalabra.objects.all(), widget=forms.RadioSelect(attrs={
+ options = [
+ ("atributos", "atributos"),
+ ("vocabulario", "vocabulario")
+ ]
+
+ self.fields['estilo_palabras'] = forms.ChoiceField(choices=options, widget=forms.RadioSelect(attrs={
"class": "uppercase text-lg tracking-wider font-medium p-2 px-4 active:px-5 transition-all rounded-xl bg-blue-500 text-white",
- }), required=True, initial=EstiloPalabra.objects.first())
+ }), required=True, initial=options[0])
self.fields['tipo_escala'] = forms.ModelChoiceField(queryset=TipoEscala.objects.all(), widget=forms.RadioSelect(attrs={
"class": "uppercase text-lg tracking-wider font-medium p-2 px-4 active:px-5 transition-all rounded-xl bg-blue-500 text-white",
diff --git a/tecnicas/forms/create_session/sesion_basic_napping.py b/tecnicas/forms/create_session/sesion_basic_napping.py
new file mode 100644
index 0000000000000000000000000000000000000000..5a6e76ef63428dfd50daaa0e4c4c2be9395a6af3
--- /dev/null
+++ b/tecnicas/forms/create_session/sesion_basic_napping.py
@@ -0,0 +1,38 @@
+from django import forms
+from tecnicas.models import Modalidad
+
+
+class SesionBasicNappingForm(forms.Form):
+ nombre_sesion = forms.CharField(max_length=255, widget=forms.TextInput(attrs={
+ "class": "bg-surface-ligt border-b-1 text-center w-full p-1",
+ "name": "nombre_sesion",
+ "placeholder": "Ej. Mermelada de mango picante"
+ }), required=False)
+
+ numero_productos = forms.IntegerField(widget=forms.NumberInput(attrs={
+ "class": "bg-surface-ligt p-1 border-b-1 text-center w-full",
+ "placeholder": "Solo números"
+ }), required=True)
+
+ numero_catadores = forms.IntegerField(widget=forms.NumberInput(attrs={
+ "class": "bg-surface-ligt p-1 border-b-1 text-center w-full",
+ "placeholder": "Solo números"
+ }), required=True)
+
+ instrucciones = forms.CharField(max_length=255, widget=forms.TextInput(attrs={
+ "class": "bg-surface-ligt border-b-1 text-center w-full p-1",
+ "placeholder": "Este campo es opcional"
+ }), required=False)
+
+ def __init__(self, *args, **kwargs):
+ super(SesionBasicNappingForm, self).__init__(*args, **kwargs)
+ names_mod = [
+ ("sin modalidad", "sin modalidad"),
+ ("sorting", "sorting"),
+ ("perfil ultra flash", "perfil ultra flash")
+ ]
+
+ self.fields['modalidad'] = forms.CharField(widget=forms.RadioSelect(choices=names_mod, attrs={
+ "class": "radio radio-lg radio-info",
+ "placeholder": "Seleccione una modalidad",
+ }), required=True, initial=names_mod[0])
diff --git a/tecnicas/forms/create_session/sesion_basic_pf_form.py b/tecnicas/forms/create_session/sesion_basic_pf_form.py
new file mode 100644
index 0000000000000000000000000000000000000000..d1609d4b8e95a3dc7cc405ca1b095f2fc7420ff2
--- /dev/null
+++ b/tecnicas/forms/create_session/sesion_basic_pf_form.py
@@ -0,0 +1,29 @@
+from django import forms
+
+
+class SesionBasicPFForm(forms.Form):
+ nombre_sesion = forms.CharField(max_length=255, widget=forms.TextInput(attrs={
+ "class": "bg-surface-ligt border-b-1 text-center w-full p-1",
+ "name": "nombre_sesion",
+ "placeholder": "Ej. Mermelada de mango picante"
+ }), required=False)
+
+ numero_productos = forms.IntegerField(widget=forms.NumberInput(attrs={
+ "class": "bg-surface-ligt p-1 border-b-1 text-center w-full",
+ "placeholder": "Solo números"
+ }), required=True)
+
+ numero_catadores = forms.IntegerField(widget=forms.NumberInput(attrs={
+ "class": "bg-surface-ligt p-1 border-b-1 text-center w-full",
+ "placeholder": "Solo números"
+ }), required=True)
+
+ numero_repeticiones = forms.IntegerField(widget=forms.NumberInput(attrs={
+ "class": "bg-surface-ligt p-1 border-b-1 text-center w-full",
+ "placeholder": "Solo números"
+ }), required=True)
+
+ instrucciones = forms.CharField(max_length=255, widget=forms.TextInput(attrs={
+ "class": "bg-surface-ligt border-b-1 text-center w-full p-1",
+ "placeholder": "Este campo es opcional"
+ }), required=False)
diff --git a/tecnicas/forms/create_session/sesion_basic_sort_form.py b/tecnicas/forms/create_session/sesion_basic_sort_form.py
new file mode 100644
index 0000000000000000000000000000000000000000..bb2f0c70775cd838a064c4670b04a2ab587d5fce
--- /dev/null
+++ b/tecnicas/forms/create_session/sesion_basic_sort_form.py
@@ -0,0 +1,24 @@
+from django import forms
+
+
+class SesionBasicSortForm(forms.Form):
+ nombre_sesion = forms.CharField(max_length=255, widget=forms.TextInput(attrs={
+ "class": "bg-surface-ligt border-b-1 text-center w-full p-1",
+ "name": "nombre_sesion",
+ "placeholder": "Ej. Mermelada de mango picante"
+ }), required=False)
+
+ numero_productos = forms.IntegerField(widget=forms.NumberInput(attrs={
+ "class": "bg-surface-ligt p-1 border-b-1 text-center w-full",
+ "placeholder": "Solo números"
+ }), required=True)
+
+ numero_catadores = forms.IntegerField(widget=forms.NumberInput(attrs={
+ "class": "bg-surface-ligt p-1 border-b-1 text-center w-full",
+ "placeholder": "Solo números"
+ }), required=True)
+
+ instrucciones = forms.CharField(max_length=255, widget=forms.TextInput(attrs={
+ "class": "bg-surface-ligt border-b-1 text-center w-full p-1",
+ "placeholder": "Este campo es opcional"
+ }), required=False)
diff --git a/tecnicas/forms/list_words_form.py b/tecnicas/forms/list_words_form.py
new file mode 100644
index 0000000000000000000000000000000000000000..ed4e3dab52d727ae24e51497c499452abf3c1775
--- /dev/null
+++ b/tecnicas/forms/list_words_form.py
@@ -0,0 +1,49 @@
+from django import forms
+import re
+
+
+class ListWordsForm(forms.Form):
+ regex = re.compile(r'^[a-zñ]{3,}$')
+
+ def __init__(self, *args, new_words=None, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ if not new_words:
+ self.fields['nombre_palabra'] = forms.CharField(
+ label='Nombre de la palabra',
+ max_length=255,
+ widget=forms.TextInput(attrs={
+ 'placeholder': 'Escribe una palabra',
+ 'class': 'cts-input-list-word bg-surface-ligt p-1 border-b-1 text-center w-full',
+ 'pattern': self.regex.pattern,
+ 'title': 'Solo letras minúsculas y la letra ñ, no poner acentos. No se permiten mayúsculas ni caracteres especiales. Mínimo 3 letras',
+ })
+ )
+ else:
+ for index, name in enumerate(new_words, start=1):
+ self.fields[f'palabra_{index}'] = forms.CharField(
+ label=f'Palabra {index}',
+ max_length=255,
+ initial=name,
+ widget=forms.TextInput(attrs={
+ 'placeholder': 'Escribe una palabra',
+ 'class': 'cts-input-list-word bg-surface-ligt p-1 border-b-1 text-center w-full',
+ 'pattern': self.regex.pattern,
+ 'title': 'Solo letras minúsculas y la letra ñ. No se permiten mayúsculas ni caracteres especiales.'
+ })
+ )
+
+ def clean(self):
+ cleaned_data = super().clean()
+ errores = []
+
+ for field_name, value in cleaned_data.items():
+ if not self.regex.match(value):
+ errores.append(f"'{value}' contiene caracteres no permitidos.")
+ elif len(value) > 255:
+ errores.append(f"'{value}' excede los 255 caracteres.")
+
+ if errores:
+ raise forms.ValidationError(errores)
+
+ return cleaned_data
diff --git a/tecnicas/migrations/0022_alter_sesionsensorial_codigo_sesion.py b/tecnicas/migrations/0022_alter_sesionsensorial_codigo_sesion.py
new file mode 100644
index 0000000000000000000000000000000000000000..83aaaeede2816bf72d0a15a16da7cfdf778662dc
--- /dev/null
+++ b/tecnicas/migrations/0022_alter_sesionsensorial_codigo_sesion.py
@@ -0,0 +1,19 @@
+# Generated by Django 5.2.1 on 2025-11-12 20:16
+
+import shortuuid.main
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tecnicas', '0021_rename_nomre_vocabulario_vocabulario_nombre_vocabulario_and_more'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='sesionsensorial',
+ name='codigo_sesion',
+ field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False),
+ ),
+ ]
diff --git a/tecnicas/migrations/0023_alter_sesionsensorial_codigo_sesion_listapalabras.py b/tecnicas/migrations/0023_alter_sesionsensorial_codigo_sesion_listapalabras.py
new file mode 100644
index 0000000000000000000000000000000000000000..2dccc7f0158c944fe9183a7ccdde5f6e94615a81
--- /dev/null
+++ b/tecnicas/migrations/0023_alter_sesionsensorial_codigo_sesion_listapalabras.py
@@ -0,0 +1,30 @@
+# Generated by Django 5.2.1 on 2025-11-12 20:24
+
+import django.db.models.deletion
+import shortuuid.main
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tecnicas', '0022_alter_sesionsensorial_codigo_sesion'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='sesionsensorial',
+ name='codigo_sesion',
+ field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False),
+ ),
+ migrations.CreateModel(
+ name='ListaPalabras',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('es_final', models.BooleanField(default=False)),
+ ('catador', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='catador_lista', to='tecnicas.catador')),
+ ('palabras', models.ManyToManyField(related_name='lista_palabras', to='tecnicas.palabra')),
+ ('tecnica', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tecnica_lista', to='tecnicas.tecnica')),
+ ],
+ ),
+ ]
diff --git a/tecnicas/migrations/0024_alter_sesionsensorial_codigo_sesion_grupoproducto.py b/tecnicas/migrations/0024_alter_sesionsensorial_codigo_sesion_grupoproducto.py
new file mode 100644
index 0000000000000000000000000000000000000000..0504ee42d9cada10afede1de43c835e2ff75b8ae
--- /dev/null
+++ b/tecnicas/migrations/0024_alter_sesionsensorial_codigo_sesion_grupoproducto.py
@@ -0,0 +1,30 @@
+# Generated by Django 5.2.1 on 2025-11-25 01:18
+
+import django.db.models.deletion
+import shortuuid.main
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tecnicas', '0023_alter_sesionsensorial_codigo_sesion_listapalabras'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='sesionsensorial',
+ name='codigo_sesion',
+ field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False),
+ ),
+ migrations.CreateModel(
+ name='GrupoProducto',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('catador', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='catador_grupo_producto', to='tecnicas.catador')),
+ ('palabras', models.ManyToManyField(related_name='grupo_producto', to='tecnicas.palabra')),
+ ('productos', models.ManyToManyField(related_name='grupo_producto', to='tecnicas.producto')),
+ ('tecnica', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tecnica_grupo_producto', to='tecnicas.tecnica')),
+ ],
+ ),
+ ]
diff --git a/tecnicas/migrations/0025_modalidad_alter_sesionsensorial_codigo_sesion_and_more.py b/tecnicas/migrations/0025_modalidad_alter_sesionsensorial_codigo_sesion_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..a33121db5d3c4990e02c52c67d47ad5de57ffc2b
--- /dev/null
+++ b/tecnicas/migrations/0025_modalidad_alter_sesionsensorial_codigo_sesion_and_more.py
@@ -0,0 +1,36 @@
+# Generated by Django 5.2.1 on 2025-11-28 19:09
+
+import django.db.models.deletion
+import shortuuid.main
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tecnicas', '0024_alter_sesionsensorial_codigo_sesion_grupoproducto'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Modalidad',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('nombre', models.CharField(max_length=255)),
+ ],
+ ),
+ migrations.AlterField(
+ model_name='sesionsensorial',
+ name='codigo_sesion',
+ field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False),
+ ),
+ migrations.CreateModel(
+ name='TecnicaModalidad',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('usado', models.BooleanField(default=False)),
+ ('modalidad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='modalidad_tecnica', to='tecnicas.modalidad')),
+ ('tecnica', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tecnica_modalidad', to='tecnicas.tecnica')),
+ ],
+ ),
+ ]
diff --git a/tecnicas/migrations/0026_tecnica_modalidad_and_more.py b/tecnicas/migrations/0026_tecnica_modalidad_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..cbdf7e2c5ac09b4c0ab8c933e7c57acdc1fd476f
--- /dev/null
+++ b/tecnicas/migrations/0026_tecnica_modalidad_and_more.py
@@ -0,0 +1,27 @@
+# Generated by Django 5.2.1 on 2025-11-28 19:39
+
+import shortuuid.main
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tecnicas', '0025_modalidad_alter_sesionsensorial_codigo_sesion_and_more'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='tecnica',
+ name='modalidad',
+ field=models.ManyToManyField(related_name='modalidad_tecnica', to='tecnicas.modalidad'),
+ ),
+ migrations.AlterField(
+ model_name='sesionsensorial',
+ name='codigo_sesion',
+ field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False),
+ ),
+ migrations.DeleteModel(
+ name='TecnicaModalidad',
+ ),
+ ]
diff --git a/tecnicas/migrations/0027_alter_sesionsensorial_codigo_sesion_and_more.py b/tecnicas/migrations/0027_alter_sesionsensorial_codigo_sesion_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..c762483c1bdbed1d21cab7a5c172773627d049f1
--- /dev/null
+++ b/tecnicas/migrations/0027_alter_sesionsensorial_codigo_sesion_and_more.py
@@ -0,0 +1,24 @@
+# Generated by Django 5.2.1 on 2025-11-28 19:59
+
+import shortuuid.main
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tecnicas', '0026_tecnica_modalidad_and_more'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='sesionsensorial',
+ name='codigo_sesion',
+ field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False),
+ ),
+ migrations.AlterField(
+ model_name='tecnica',
+ name='modalidad',
+ field=models.ManyToManyField(blank=True, null=True, related_name='modalidad_tecnica', to='tecnicas.modalidad'),
+ ),
+ ]
diff --git a/tecnicas/migrations/0028_alter_sesionsensorial_codigo_sesion_and_more.py b/tecnicas/migrations/0028_alter_sesionsensorial_codigo_sesion_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..21d02655ac3c77976799d744ece7d7afe374b0b3
--- /dev/null
+++ b/tecnicas/migrations/0028_alter_sesionsensorial_codigo_sesion_and_more.py
@@ -0,0 +1,24 @@
+# Generated by Django 5.2.1 on 2025-11-28 20:10
+
+import shortuuid.main
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tecnicas', '0027_alter_sesionsensorial_codigo_sesion_and_more'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='sesionsensorial',
+ name='codigo_sesion',
+ field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False),
+ ),
+ migrations.AlterField(
+ model_name='tecnica',
+ name='modalidad',
+ field=models.ManyToManyField(blank=True, related_name='modalidad_tecnica', to='tecnicas.modalidad'),
+ ),
+ ]
diff --git a/tecnicas/migrations/0029_remove_tecnica_modalidad_and_more.py b/tecnicas/migrations/0029_remove_tecnica_modalidad_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..a6b02511e78df6d82c26aa79239e2d32d38e3bf6
--- /dev/null
+++ b/tecnicas/migrations/0029_remove_tecnica_modalidad_and_more.py
@@ -0,0 +1,23 @@
+# Generated by Django 5.2.1 on 2025-11-28 21:48
+
+import shortuuid.main
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tecnicas', '0028_alter_sesionsensorial_codigo_sesion_and_more'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='tecnica',
+ name='modalidad',
+ ),
+ migrations.AlterField(
+ model_name='sesionsensorial',
+ name='codigo_sesion',
+ field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False),
+ ),
+ ]
diff --git a/tecnicas/migrations/0030_alter_sesionsensorial_codigo_sesion_tecnicamodalidad.py b/tecnicas/migrations/0030_alter_sesionsensorial_codigo_sesion_tecnicamodalidad.py
new file mode 100644
index 0000000000000000000000000000000000000000..cee3160ac6460265bca627a76c20212b58412d94
--- /dev/null
+++ b/tecnicas/migrations/0030_alter_sesionsensorial_codigo_sesion_tecnicamodalidad.py
@@ -0,0 +1,29 @@
+# Generated by Django 5.2.1 on 2025-11-28 21:54
+
+import django.db.models.deletion
+import shortuuid.main
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tecnicas', '0029_remove_tecnica_modalidad_and_more'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='sesionsensorial',
+ name='codigo_sesion',
+ field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False),
+ ),
+ migrations.CreateModel(
+ name='TecnicaModalidad',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('usando', models.BooleanField(default=False)),
+ ('modalidad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='modalidad_tecnica', to='tecnicas.modalidad')),
+ ('tecnica', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tecnica_modalidad', to='tecnicas.tecnica')),
+ ],
+ ),
+ ]
diff --git a/tecnicas/migrations/0031_calificacion_palabras_and_more.py b/tecnicas/migrations/0031_calificacion_palabras_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..babb767f1ce8d476d009b2eccb3c5913fdb9bf38
--- /dev/null
+++ b/tecnicas/migrations/0031_calificacion_palabras_and_more.py
@@ -0,0 +1,34 @@
+# Generated by Django 5.2.1 on 2025-11-29 03:05
+
+import django.db.models.deletion
+import shortuuid.main
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tecnicas', '0030_alter_sesionsensorial_codigo_sesion_tecnicamodalidad'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='calificacion',
+ name='palabras',
+ field=models.ManyToManyField(blank=True, related_name='calificacion_palabras', to='tecnicas.palabra'),
+ ),
+ migrations.AlterField(
+ model_name='sesionsensorial',
+ name='codigo_sesion',
+ field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False),
+ ),
+ migrations.CreateModel(
+ name='DatoPunto',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('x', models.FloatField(default=0)),
+ ('y', models.FloatField(default=0)),
+ ('calificacion', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='data_punto', to='tecnicas.calificacion')),
+ ],
+ ),
+ ]
diff --git a/tecnicas/models/__init__.py b/tecnicas/models/__init__.py
index 769e8f584dac610a81d03a42491d7ffa0a0974a7..c30014a8d0710168865efad2299b3c835c2c31ba 100644
--- a/tecnicas/models/__init__.py
+++ b/tecnicas/models/__init__.py
@@ -3,6 +3,7 @@ from .etiqueta import Etiqueta
from .tipo_escala import TipoEscala
from .tipo_tecnica import TipoTecnica
from .estilo_palabra import EstiloPalabra
+from .modalidad import Modalidad
from .presentador import Presentador
from .catador import Catador
@@ -27,4 +28,9 @@ from .dato_valor import ValorBooleano
from .orden import Orden
from .orden import Posicion
-from .participacion import Participacion
\ No newline at end of file
+from .participacion import Participacion
+
+from .lista_palabras import ListaPalabras
+from .grupo_producto import GrupoProducto
+from .tecnica_modalidad import TecnicaModalidad
+from .dato_punto import DatoPunto
\ No newline at end of file
diff --git a/tecnicas/models/calificacion.py b/tecnicas/models/calificacion.py
index b2ac5d4189b7ee41aeb5739093b6c59e20ed1ae8..f73d7c08fdc839a060b4879868d60df0adfc7d61 100644
--- a/tecnicas/models/calificacion.py
+++ b/tecnicas/models/calificacion.py
@@ -3,12 +3,20 @@ from django.db import models
from .producto import Producto
from .tecnica import Tecnica
from .catador import Catador
+from .palabra import Palabra
+
class Calificacion(models.Model):
num_repeticion = models.IntegerField()
- id_producto = models.ForeignKey(Producto, on_delete=models.CASCADE, related_name="calificacion_producto")
- id_tecnica = models.ForeignKey(Tecnica, on_delete=models.CASCADE, related_name="calificacion_tecnica")
- id_catador = models.ForeignKey(Catador, on_delete=models.CASCADE, related_name="calificacion_catador")
+ id_producto = models.ForeignKey(
+ Producto, on_delete=models.CASCADE, related_name="calificacion_producto")
+ id_tecnica = models.ForeignKey(
+ Tecnica, on_delete=models.CASCADE, related_name="calificacion_tecnica")
+ id_catador = models.ForeignKey(
+ Catador, on_delete=models.CASCADE, related_name="calificacion_catador")
+
+ palabras = models.ManyToManyField(
+ Palabra, related_name="calificacion_palabras", blank=True)
def __str__(self):
- return f"{self.id} - {self.id_tecnica.sesion_tecnica} - {self.num_repeticion} - {self.id_catador.user.username}"
\ No newline at end of file
+ return f"{self.id} - {self.id_tecnica.sesion_tecnica} - {self.num_repeticion} - {self.id_catador.user.username}"
diff --git a/tecnicas/models/dato_punto.py b/tecnicas/models/dato_punto.py
new file mode 100644
index 0000000000000000000000000000000000000000..f0435c8ac60d31da0f6e6735b00c8fc4b47b0561
--- /dev/null
+++ b/tecnicas/models/dato_punto.py
@@ -0,0 +1,12 @@
+from django.db import models
+from .calificacion import Calificacion
+
+
+class DatoPunto(models.Model):
+ x = models.FloatField(null=False, default=0)
+ y = models.FloatField(null=False, default=0)
+ calificacion = models.ForeignKey(
+ Calificacion, on_delete=models.CASCADE, related_name="data_punto")
+
+ def __str__(self):
+ return f"({self.calificacion.id_producto.codigoProducto}: {self.x}, {self.y})"
diff --git a/tecnicas/models/grupo_producto.py b/tecnicas/models/grupo_producto.py
new file mode 100644
index 0000000000000000000000000000000000000000..ae21abdb5ef2d69322bf8dec4880b74d7f5bf166
--- /dev/null
+++ b/tecnicas/models/grupo_producto.py
@@ -0,0 +1,19 @@
+from django.db import models
+from tecnicas.models import Tecnica, Catador, Palabra, Producto
+
+
+class GrupoProducto(models.Model):
+ tecnica = models.ForeignKey(
+ Tecnica, on_delete=models.CASCADE, related_name="tecnica_grupo_producto")
+
+ catador = models.ForeignKey(
+ Catador, on_delete=models.CASCADE, related_name="catador_grupo_producto")
+
+ productos = models.ManyToManyField(
+ Producto, related_name="grupo_producto")
+
+ palabras = models.ManyToManyField(
+ Palabra, related_name="grupo_producto")
+
+ def __str__(self):
+ return f"{self.tecnica.sesion_tecnica.codigo_sesion} - {self.catador.user.username}"
diff --git a/tecnicas/models/lista_palabras.py b/tecnicas/models/lista_palabras.py
new file mode 100644
index 0000000000000000000000000000000000000000..ca39976f7d7a53b0fa9824239ef5668b5d857a06
--- /dev/null
+++ b/tecnicas/models/lista_palabras.py
@@ -0,0 +1,15 @@
+from django.db import models
+from tecnicas.models import Tecnica, Catador, Palabra
+
+
+class ListaPalabras(models.Model):
+ tecnica = models.ForeignKey(
+ Tecnica, on_delete=models.CASCADE, related_name="tecnica_lista")
+ catador = models.ForeignKey(
+ Catador, on_delete=models.CASCADE, related_name="catador_lista")
+ es_final = models.BooleanField(default=False)
+ palabras = models.ManyToManyField(
+ Palabra, related_name="lista_palabras")
+
+ def __str__(self):
+ return f"{self.tecnica.sesion_tecnica.codigo_sesion} - {self.catador.user.username}"
diff --git a/tecnicas/models/modalidad.py b/tecnicas/models/modalidad.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ccd258cc698882591e8b7bc3d7990137c36573c
--- /dev/null
+++ b/tecnicas/models/modalidad.py
@@ -0,0 +1,8 @@
+from django.db import models
+
+
+class Modalidad(models.Model):
+ nombre = models.CharField(max_length=255)
+
+ def __str__(self):
+ return self.nombre
diff --git a/tecnicas/models/tecnica.py b/tecnicas/models/tecnica.py
index 5305332c62b75c9e4e0ae5c11d37359f187320ca..7d486fe5f5064d6c8a619e003ad055b9db22f371 100644
--- a/tecnicas/models/tecnica.py
+++ b/tecnicas/models/tecnica.py
@@ -2,6 +2,7 @@ from django.db import models
from .estilo_palabra import EstiloPalabra
from .tipo_tecnica import TipoTecnica
+from .modalidad import Modalidad
class Tecnica(models.Model):
diff --git a/tecnicas/models/tecnica_modalidad.py b/tecnicas/models/tecnica_modalidad.py
new file mode 100644
index 0000000000000000000000000000000000000000..e9167a4845468b6d82bcb8b5630b5b3c16870e76
--- /dev/null
+++ b/tecnicas/models/tecnica_modalidad.py
@@ -0,0 +1,12 @@
+from django.db import models
+from .tecnica import Tecnica
+from .modalidad import Modalidad
+
+
+class TecnicaModalidad(models.Model):
+ tecnica = models.ForeignKey(
+ Tecnica, on_delete=models.CASCADE, related_name="tecnica_modalidad")
+ modalidad = models.ForeignKey(
+ Modalidad, on_delete=models.CASCADE, related_name="modalidad_tecnica")
+
+ usando = models.BooleanField(default=False)
diff --git a/tecnicas/static/img/words.webp b/tecnicas/static/img/words.webp
new file mode 100644
index 0000000000000000000000000000000000000000..0be9e89c4bc74e461a8a7aa7ff14599e1aee81e8
Binary files /dev/null and b/tecnicas/static/img/words.webp differ
diff --git a/tecnicas/static/js/actions-form.js b/tecnicas/static/js/actions-form.js
index f755a4313127d6006ce9a2cddaeed2d3b32a84c3..e222ebb4a88c7ea85129f9066638f27761293134 100644
--- a/tecnicas/static/js/actions-form.js
+++ b/tecnicas/static/js/actions-form.js
@@ -3,4 +3,12 @@ function exit_sesion(styleClass) {
const action = form.querySelector(".action-input");
action.value = "exit_session";
form.submit();
-}
\ No newline at end of file
+}
+
+function finishSession(styleClass) {
+ const form = document.querySelector(`.${styleClass}`);
+ form.action = ""
+ const action = form.querySelector(".action-input");
+ action.value = "finish_session";
+ form.submit();
+}
diff --git a/tecnicas/static/js/created-scale.js b/tecnicas/static/js/created-scale.js
index 7854f9d6cb5e0a45aab08d577edb4dc82fca7d6a..87fc46af34d563bcdfc71c2c90ffc97ee69d46f7 100644
--- a/tecnicas/static/js/created-scale.js
+++ b/tecnicas/static/js/created-scale.js
@@ -44,7 +44,7 @@ async function sendRating(word) {
const formRatingWord = document.querySelector(`.form-rating-${word}`);
const dataForm = new FormData(formRatingWord);
- const url = "/cata/testers/api/ratingword";
+ const url = "/cata/testers/api/ratingword/escalas";
const codeProduct = document
.querySelector(".ct-product-rating")
diff --git a/tecnicas/static/js/created-vocabulary.js b/tecnicas/static/js/created-vocabulary.js
index c5c25be883c35eb4dcfb7adb6d00cb1a3f585215..7f9d388d3fc8043e7b8bcad3861e6f4036e36ea7 100644
--- a/tecnicas/static/js/created-vocabulary.js
+++ b/tecnicas/static/js/created-vocabulary.js
@@ -1,7 +1,7 @@
// **************************************
// Create Vocabulary
// **************************************
-async function submitSelectWords(classNanmeForm, update = false) {
+async function submitSelectWords(classNanmeForm) {
const form = document.querySelector(`.${classNanmeForm}`);
const name_vocabulary = form.querySelector(".cts-name-voca").value;
@@ -20,29 +20,7 @@ async function submitSelectWords(classNanmeForm, update = false) {
wordsInput.name = "words";
wordsInput.value = JSON.stringify(listWordsSelect);
- const [isUpdata, orinalName] = inputIsUpdateVocabulary(update);
-
form.appendChild(wordsInput);
- form.appendChild(isUpdata);
- if (orinalName) form.appendChild(orinalName);
form.submit();
}
-
-function inputIsUpdateVocabulary(is_update = false) {
- const isUpdata = document.createElement("input");
- isUpdata.type = "hidden";
- isUpdata.name = "is_update";
- isUpdata.value = is_update;
-
- if (is_update) {
- const orinalName = document.querySelector(".cts-original-name").textContent;
- const inputName = document.createElement("input");
- inputName.type = "hidden";
- inputName.name = "original_name";
- inputName.value = orinalName;
- return [isUpdata, inputName];
- }
-
- return [isUpdata, is_update];
-}
diff --git a/tecnicas/static/js/details-session.js b/tecnicas/static/js/details-session.js
index fe403c00daa302d9ab518c778aa56d03d87d98bb..6e24f2549a66e442cd25035c624de9954ed5d372 100644
--- a/tecnicas/static/js/details-session.js
+++ b/tecnicas/static/js/details-session.js
@@ -17,4 +17,12 @@ function deleteSession() {
const input = actionForm.querySelector(".action-option")
input.value = "delete_session";
actionForm.submit();
-}
\ No newline at end of file
+}
+
+function startSession(nameMode) {
+ const nameUnderscort = nameMode.replaceAll(" ", "_");
+ console.log(nameUnderscort);
+ const input = actionForm.querySelector(".action-option")
+ input.value = `start_${nameUnderscort}`;
+ actionForm.submit();
+}
diff --git a/tecnicas/static/js/download-table-csv.js b/tecnicas/static/js/download-table-csv.js
index 1e525453c2a5aa0bf5fbe1a55babcf923d65a13a..27afdf85556badce4f412b9da43405f8d50c8f16 100644
--- a/tecnicas/static/js/download-table-csv.js
+++ b/tecnicas/static/js/download-table-csv.js
@@ -4,7 +4,7 @@ document.addEventListener("DOMContentLoaded", function () {
btn.addEventListener("click", function () {
// Try set the table in the page
- let table = document.getElementById("convencional-table");
+ let table = document.getElementById("generic-donwload-table");
if (!table) {
const section = btn.closest("section");
if (section) table = section.querySelector("table");
diff --git a/tecnicas/static/js/download-table-sort-csv.js b/tecnicas/static/js/download-table-sort-csv.js
new file mode 100644
index 0000000000000000000000000000000000000000..0253a18129d6369de6a96b5b99e841800f292b03
--- /dev/null
+++ b/tecnicas/static/js/download-table-sort-csv.js
@@ -0,0 +1,79 @@
+document.addEventListener("DOMContentLoaded", function () {
+ const btn = document.getElementById("download-csv-btn");
+ if (!btn) return;
+
+ btn.addEventListener("click", function () {
+ // Try set the table in the page
+ let table = document.getElementById("generic-donwload-table");
+ if (!table) {
+ const section = btn.closest("section");
+ if (section) table = section.querySelector("table");
+ }
+ if (!table) {
+ console.warn("No se encontró la tabla para descargar.");
+ return;
+ }
+
+ // helper to trim and normalize cell text
+ const cellText = (cell) => {
+ if (!cell) return "";
+ return String(cell.textContent || "").trim();
+ };
+
+ // Collect headers
+ const headers = [];
+ const ths = table.querySelectorAll("thead th");
+ ths.forEach((th) => headers.push(cellText(th)));
+
+ // Collect rows
+ const rows = [];
+ const trs = table.querySelectorAll("tbody tr");
+ trs.forEach((tr) => {
+ const cols = [];
+ const tds = tr.querySelectorAll("td");
+ tds.forEach((td) => cols.push(cellText(td)));
+ rows.push(cols);
+ });
+
+ // Convert to CSV string (escape quotes, wrap in quotes if needed)
+ const escapeValue = (val) => {
+ if (val == null) return "";
+ let normalVal = val.replace(/[\u0300-\u036f]/g, "");
+ normalVal = normalVal.replace(/(\r\n|\n|\r|\s)/gm, '');
+ const needsQuotes = /[",\n,;]/.test(normalVal);
+ let v = normalVal.replace(/"/g, '""');
+ if (needsQuotes) v = `"${v}"`;
+ return v;
+ };
+
+ const lines = [];
+ if (headers.length) lines.push(headers.map(escapeValue).join(","));
+ rows.forEach((r) => lines.push(r.map(escapeValue).join(",")));
+
+ const csvContent = lines.join("\n");
+
+ // File name: data_{nombre_sesion or codigo_sesion}
+ const rawName = (btn.dataset.sessionName || "").trim();
+ const code = (btn.dataset.sessionCode || "").trim() || "session";
+ const namePart = rawName
+ ? rawName.replace(/[^a-zA-Z0-9-_áéíóúÁÉÍÓÚ ]/g, "").replace(/\s+/g, "_")
+ : code;
+ const fileName = `data_${namePart}.csv`;
+
+ // Create blob and force download
+ const blob = new Blob([csvContent], { type: "text/csv;charset=UTF-8;" });
+ if (navigator.msSaveBlob) {
+ navigator.msSaveBlob(blob, fileName);
+ } else {
+ const link = document.createElement("a");
+ const url = URL.createObjectURL(blob);
+ link.setAttribute("href", url);
+ link.setAttribute("download", fileName);
+ link.style.visibility = "hidden";
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ URL.revokeObjectURL(url);
+ }
+ });
+});
diff --git a/tecnicas/static/js/download-table-xlsx.js b/tecnicas/static/js/download-table-xlsx.js
new file mode 100644
index 0000000000000000000000000000000000000000..ef88ce71d72f9f00f966f42161282fae0b6f8df9
--- /dev/null
+++ b/tecnicas/static/js/download-table-xlsx.js
@@ -0,0 +1,70 @@
+document.addEventListener("DOMContentLoaded", function () {
+ const btn = document.getElementById("download-xls-btn");
+ if (!btn) return;
+
+ btn.addEventListener("click", async function () {
+ if (typeof XLSX === "undefined") {
+ alert(
+ "Librerías XLSX no están cargadas."
+ );
+ return;
+ }
+
+ const BOOK = XLSX.utils.book_new();
+
+ const sections = document.querySelectorAll("section[data-tester]");
+ if (!sections || sections.length === 0) {
+ alert("No hay tablas para descargar.");
+ return;
+ }
+
+ let fileXlsxName = "";
+
+ sections.forEach((sec) => {
+ const tester = sec.getAttribute("data-tester") || "tester";
+ const sessionName = sec.getAttribute("data-session-name") || "";
+ const sessionCode = sec.getAttribute("data-session-code") || "";
+
+ const sheetName = `${tester}`;
+
+ fileXlsxName = `datos_sesion_${sessionName ? sessionName.trim() : sessionCode.trim()
+ }`;
+
+ const table = sec.querySelector("table");
+ if (!table) return;
+
+ // Build CSV
+ const rows = [];
+ const headerCells = Array.from(table.querySelectorAll("thead tr th"));
+ const headers = headerCells.map((h) => h.textContent.trim());
+ rows.push(headers.map(escapeCsv).join(","));
+
+ const trs = table.querySelectorAll("tbody tr");
+ trs.forEach((tr) => {
+ const tds = Array.from(tr.querySelectorAll("td"));
+ const values = tds.map((td) => escapeCsv(td.textContent.trim()));
+ rows.push(values.join(","));
+ });
+
+ const csvContent = rows.join("\r\n");
+ const sheet = XLSX.read(csvContent, { type: "string" }).Sheets["Sheet1"];
+ XLSX.utils.book_append_sheet(BOOK, sheet, sheetName);
+ });
+
+ try {
+ XLSX.writeFile(BOOK, `${fileXlsxName}.xlsx`);
+ } catch (err) {
+ console.error(err);
+ alert("Error al generar el archivo XLSX: " + err.message);
+ }
+ });
+
+ const escapeCsv = (val) => {
+ if (val == null) return "";
+ let normalVal = val.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
+ const needsQuotes = /[",\n,]/.test(normalVal);
+ let v = String(normalVal).replace(/"/g, '""');
+ if (needsQuotes) v = `"${v}"`;
+ return v;
+ };
+});
diff --git a/tecnicas/static/js/download-table-zip.js b/tecnicas/static/js/download-table-zip.js
new file mode 100644
index 0000000000000000000000000000000000000000..b0a1a7769eaf3b76b1b4eaa69b288dbe1f1c992e
--- /dev/null
+++ b/tecnicas/static/js/download-table-zip.js
@@ -0,0 +1,74 @@
+document.addEventListener("DOMContentLoaded", function () {
+ const btn = document.getElementById("download-csv-btn");
+ if (!btn) return;
+
+ btn.addEventListener("click", async function () {
+ if (typeof JSZip === "undefined" || typeof saveAs === "undefined") {
+ alert(
+ "Librerías JSZip o FileSaver no están cargadas. Asegúrate de incluir los CDN de JSZip y FileSaver."
+ );
+ return;
+ }
+
+ const sections = document.querySelectorAll("section[data-tester]");
+ if (!sections || sections.length === 0) {
+ alert("No hay tablas para descargar.");
+ return;
+ }
+
+ const zip = new JSZip();
+
+ let zipName = "";
+
+ sections.forEach((sec) => {
+ const tester = sec.getAttribute("data-tester") || "tester";
+ const sessionName = sec.getAttribute("data-session-name") || "";
+ const sessionCode = sec.getAttribute("data-session-code") || "";
+
+ const fileName = `${tester}_${
+ sessionName ? sessionName.trim() : sessionCode.trim()
+ }`;
+
+ zipName = `datos_sesion_${
+ sessionName ? sessionName.trim() : sessionCode.trim()
+ }`;
+
+ const table = sec.querySelector("table");
+ if (!table) return;
+
+ // Build CSV
+ const rows = [];
+ const headerCells = Array.from(table.querySelectorAll("thead tr th"));
+ const headers = headerCells.map((h) => h.textContent.trim());
+ rows.push(headers.map(escapeCsv).join(","));
+
+ const trs = table.querySelectorAll("tbody tr");
+ trs.forEach((tr) => {
+ const tds = Array.from(tr.querySelectorAll("td"));
+ const values = tds.map((td) => escapeCsv(td.textContent.trim()));
+ rows.push(values.join(","));
+ });
+
+ const csvContent = rows.join("\r\n");
+ zip.file(`${fileName}.csv`, csvContent);
+ });
+
+ try {
+ const blob = await zip.generateAsync({ type: "blob" });
+ const zipNameWithExtension = zipName + ".zip";
+ saveAs(blob, zipNameWithExtension);
+ } catch (err) {
+ console.error(err);
+ alert("Error al generar el ZIP: " + err.message);
+ }
+ });
+
+ const escapeCsv = (val) => {
+ if (val == null) return "";
+ let normalVal = val.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
+ const needsQuotes = /[",\n,]/.test(normalVal);
+ let v = String(normalVal).replace(/"/g, '""');
+ if (needsQuotes) v = `"${v}"`;
+ return v;
+ };
+});
diff --git a/tecnicas/static/js/lists-words-testers.js b/tecnicas/static/js/lists-words-testers.js
new file mode 100644
index 0000000000000000000000000000000000000000..9c2f1ae1ba85b9fc5a72ec7fdaf65e39a5109419
--- /dev/null
+++ b/tecnicas/static/js/lists-words-testers.js
@@ -0,0 +1,103 @@
+async function getListWordsTesters() {
+ const URL = "/cata/testers/api/ratingword/pf/list";
+ try {
+ const response = await fetch(URL, {
+ method: "GET",
+ });
+
+ if (!response.ok) {
+ spanNotifaction("Fallo con la respuesta recibida");
+ return false;
+ }
+
+ const result = await response.json();
+ const messError = result.error;
+ if (messError) {
+ spanNotifaction(messError);
+ const containerWords = document.querySelector(".cts-content-list-words");
+ containerWords.innerHTML = `
+
+ ${messError}
+
`;
+ return false;
+ }
+ spanNotifaction(result.message, false);
+ const containerWords = document.querySelector(".cts-content-list-words");
+ containerWords.innerHTML = "";
+
+ const listWordsTesters = result.lists_words;
+
+ listWordsTesters.forEach((listTester) => {
+ const username = listTester.username;
+ const words = listTester.words;
+ const status = listTester.status;
+ const listDiv = createListWords(username, words, status);
+ containerWords.appendChild(listDiv);
+ });
+
+ return true;
+ } catch (error) {
+ console.error(error);
+ spanNotifaction("Error del servidor con la API");
+ return false;
+ }
+}
+
+function createListWords(username, words, status = "indefinido") {
+ const div = document.createElement("div");
+ const paragraph = document.createElement("p");
+ const paragraphStatus = document.createElement("p");
+ const ul = document.createElement("ul");
+
+ div.classList.add(
+ "cts-item-list-tester",
+ "bg-surface-sweet",
+ "px-4",
+ "pt-2",
+ "pb-3",
+ "rounded",
+ "shrink-0",
+ "w-64",
+ "space-y-2"
+ );
+
+ paragraph.classList.add(
+ "bg-surface-card",
+ "text-lg",
+ "font-semibold",
+ "text-center",
+ "rounded"
+ );
+
+ paragraphStatus.classList.add(
+ "bg-surface-card",
+ "text-lg",
+ "font-semibold",
+ "text-center",
+ "rounded"
+ );
+
+ paragraph.textContent = username;
+ paragraphStatus.textContent = status;
+
+ ul.classList.add("text-center", "grid", "grid-cols-2", "gap-2", "w-full");
+
+ words.forEach((word) => {
+ const li = document.createElement("li");
+ li.classList.add(
+ "bg-surface-ligt",
+ "rounded",
+ "font-bold",
+ "py-1",
+ "px-2",
+ "truncate"
+ );
+ li.textContent = word.nombre_palabra;
+ ul.appendChild(li);
+ });
+
+ div.appendChild(paragraph);
+ div.appendChild(paragraphStatus);
+ div.appendChild(ul);
+ return div;
+}
diff --git a/tecnicas/static/js/panel-basic-cata.js b/tecnicas/static/js/panel-basic-cata.js
index ede4704608b11de3dbbe83f9638f2b2b2bee7f89..a1fbe92e44d91ff6223cc967ca28a9e739d923e5 100644
--- a/tecnicas/static/js/panel-basic-cata.js
+++ b/tecnicas/static/js/panel-basic-cata.js
@@ -10,6 +10,7 @@ let radiosStyleWords;
function initRadiosStyleWords() {
radiosStyleWords = document.getElementsByName("estilo_palabras");
+ console.log(radiosStyleWords);
for (let index = 0; index < radiosStyleWords.length; index++) {
const radio = radiosStyleWords[index];
diff --git a/tecnicas/static/js/panel-basic.js b/tecnicas/static/js/panel-basic.js
index 7520d294328b676bc274ccb3a65d30edadb8744b..2dcb4d201c08baee77e1ba03d86132664499518f 100644
--- a/tecnicas/static/js/panel-basic.js
+++ b/tecnicas/static/js/panel-basic.js
@@ -22,12 +22,13 @@ initPanel();
function initPanel() {
initRadios();
+ initSizeOptions();
}
function initRadios() {
- inputsScale = document.getElementsByName("tipo_escala");
inputTamano = document.getElementsByName("tamano_escala").item(0);
- sizeOptionsContainer = document.getElementsByClassName("cts-options-size-scale")[0];
+ inputsScale = document.getElementsByName("tipo_escala");
+ inputsScale.item(0).checked = true;
for (let index = 0; index < inputsScale.length; index++) {
let parent = inputsScale.item(index).parentElement;
@@ -52,14 +53,16 @@ function initRadios() {
showDescriptionStyle(parent);
}
}
-
- initSizeOptions();
}
function initSizeOptions() {
+ sizeOptionsContainer = document.getElementsByClassName(
+ "cts-options-size-scale"
+ )[0];
+
for (let i = 0; i < inputsScale.length; i++) {
const radio = inputsScale.item(i);
- radio.addEventListener('change', () => {
+ radio.addEventListener("change", () => {
const tag = getTagFromLabel(radio);
populateSizeOptions(tag);
});
@@ -69,17 +72,21 @@ function initSizeOptions() {
}
}
- const form = document.querySelector('form');
+ const form = document.querySelector("form");
if (!form) return;
- form.addEventListener('submit', (e) => {
- const chosen = document.querySelector('input[name="option_size_scale"]:checked');
+ form.addEventListener("submit", (e) => {
+ const chosen = document.querySelector(
+ 'input[name="option_size_scale"]:checked'
+ );
if (chosen && inputTamano) {
inputTamano.value = chosen.value;
}
if (sizeOptionsContainer) {
- const toRemove = sizeOptionsContainer.querySelectorAll('input[name="option_size_scale"]');
+ const toRemove = sizeOptionsContainer.querySelectorAll(
+ 'input[name="option_size_scale"]'
+ );
toRemove.forEach((el) => el.remove());
}
});
@@ -88,37 +95,42 @@ function initSizeOptions() {
function getTagFromLabel(radio) {
try {
const parent = radio.parentElement;
- if (!parent) return '';
- const text = parent.textContent || '';
+ if (!parent) return "";
+ const text = parent.textContent || "";
return text.trim().split(/\s+/)[0].toLowerCase();
} catch (err) {
- return '';
+ return "";
}
}
function populateSizeOptions(tag) {
- const options = SIZE_OPTIONS[tag] || SIZE_OPTIONS['estructurada'];
+ const options = SIZE_OPTIONS[tag] || SIZE_OPTIONS["estructurada"];
if (!sizeOptionsContainer) return;
- sizeOptionsContainer.innerHTML = '';
+ sizeOptionsContainer.innerHTML = "";
options.forEach((val) => {
- const label = document.createElement('label');
- label.className = 'flex flex-col items-center cursor-pointer';
+ const label = document.createElement("label");
+ label.className = "flex flex-col items-center cursor-pointer";
- const input = document.createElement('input');
- input.type = 'radio';
- input.name = 'option_size_scale';
+ const input = document.createElement("input");
+ input.type = "radio";
+ input.name = "option_size_scale";
input.value = String(val);
- input.className = 'radio radio-lg checked:bg-pink-500';
+ input.className = "radio radio-lg checked:bg-pink-500";
- const span = document.createElement('span');
- span.className = 'mt-2 text-xl text-gray-700 font-medium';
+ const span = document.createElement("span");
+ span.className = "mt-2 text-xl text-gray-700 font-medium";
span.textContent = String(val);
label.appendChild(input);
label.appendChild(span);
sizeOptionsContainer.appendChild(label);
});
+
+ const firstRadioSize = document
+ .getElementsByName("option_size_scale")
+ .item(0);
+ firstRadioSize.checked = true;
}
function showDescriptionStyle(label) {
diff --git a/tecnicas/static/js/pf-make-list.js b/tecnicas/static/js/pf-make-list.js
new file mode 100644
index 0000000000000000000000000000000000000000..62ea9d99c4a51486cefe793a07d1063e6adbeca2
--- /dev/null
+++ b/tecnicas/static/js/pf-make-list.js
@@ -0,0 +1,227 @@
+const FORM_DESCRIBE = document.querySelector(".cts-form-pf-word");
+const BOX_WORDS = document.querySelector(".cts-box-words");
+const IMG_LIST = document.querySelector(".cts-img-list");
+const ERROR_INPUT_WORD = document.querySelector(".error-input-word");
+const FORM_ACTION = document.querySelector(".form-actions");
+
+const WORDS = [];
+const STYLES_LI = [
+ "cts-item-words",
+ "bg-gray-400",
+ "text-black",
+ "rounded",
+ "font-bold",
+ "text-lg",
+ "px-4",
+ "py-3",
+ "flex",
+ "flex-wrap",
+ "flex-row",
+ "flex-1",
+ "min-w-fit",
+ "justify-center",
+ "items-center",
+ "gap-3",
+];
+
+const STYLES_BTN = [
+ "cts-remove-word",
+ "px-4",
+ "border-b-2",
+ "active:border-b-0",
+ "active:border-t-2",
+ "transition-all",
+ "rounded-xl",
+ "font-black",
+ "w-fit",
+ "capitalize",
+ "active:border-red-500",
+ "border-red-800",
+ "bg-red-500",
+];
+
+const itemWord = (wordName, index) => {
+ const btn = document.createElement("button");
+ btn.setAttribute("data-index", index);
+ btn.classList.add(...STYLES_BTN);
+ btn.textContent = "➖";
+
+ const ph = document.createElement("p");
+ ph.classList.add("ct-word-received");
+ ph.textContent = wordName;
+
+ const li = document.createElement("li");
+ li.setAttribute("id", `word-${index}`);
+ li.classList.add(...STYLES_LI);
+
+ li.appendChild(ph);
+ li.appendChild(btn);
+
+ return li;
+};
+
+function initWordsFromBox() {
+ if (!BOX_WORDS) return;
+ const current_words = BOX_WORDS.querySelectorAll(".cts-item-words");
+ if (!current_words.length) return;
+
+ WORDS.length = 0;
+ current_words.forEach((li) => {
+ const p = li.querySelector(".ct-word-received");
+ const text = p ? p.textContent.trim() : li.textContent.trim();
+ if (text) WORDS.push(text);
+ });
+ renderWords();
+}
+
+function renderWords() {
+ if (!BOX_WORDS) return;
+ if (!WORDS.length) {
+ BOX_WORDS.innerHTML = "";
+ IMG_LIST.classList.remove("hidden");
+ BOX_WORDS.appendChild(IMG_LIST);
+ return;
+ }
+
+ BOX_WORDS.innerHTML = "";
+ WORDS.forEach((word, index) => {
+ let liElement = itemWord(word, index);
+ BOX_WORDS.appendChild(liElement);
+ });
+
+ const removeButtons = BOX_WORDS.querySelectorAll("button.cts-remove-word");
+ removeButtons.forEach((btn) => {
+ btn.addEventListener("click", (e) => {
+ const idx = parseInt(btn.dataset.index);
+ if (!Number.isNaN(idx)) {
+ WORDS.splice(idx, 1);
+ renderWords();
+ }
+ });
+ });
+}
+
+function setupDescribeFormToAddWord() {
+ if (!FORM_DESCRIBE) return;
+ FORM_DESCRIBE.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ if (!FORM_DESCRIBE.reportValidity()) {
+ return;
+ }
+
+ const input = FORM_DESCRIBE.querySelector('input[type="text"]');
+ if (!input) return;
+
+ const value = input.value.trim();
+ if (!value) return;
+
+ if (WORDS.includes(value)) {
+ spanNotifaction("Esa palabra ya está en la lista");
+ return;
+ }
+
+ WORDS.push(value);
+ renderWords();
+
+ input.value = "";
+ input.focus();
+ });
+}
+
+function spanNotifaction(messageError, isError = true) {
+ const span = document.createElement("span");
+ span.textContent = messageError;
+
+ const div = document.createElement("div");
+ div.classList.add("alert", isError ? "alert-error" : "alert-success");
+ div.appendChild(span);
+
+ ERROR_INPUT_WORD.append(div);
+
+ setTimeout(() => {
+ ERROR_INPUT_WORD.removeChild(div);
+ }, 3000);
+}
+
+async function sendWordsToSave() {
+ if (!WORDS.length) {
+ spanNotifaction("Debe existir al menos una palabra en la lista");
+ return false;
+ }
+
+ const currentPhase = parseInt(
+ document.querySelector(".cts-phase-pf").dataset.phase
+ );
+
+ const csrfToken = document.querySelector("[name=csrfmiddlewaretoken]").value;
+
+ const requestData = {
+ phase: currentPhase,
+ words: WORDS,
+ };
+
+ const URL = "/cata/testers/api/ratingword/pf/list";
+
+ try {
+ const response = await fetch(URL, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "X-CSRFToken": csrfToken,
+ "X-Requested-With": "XMLHttpRequest",
+ },
+ body: JSON.stringify(requestData),
+ });
+
+ if (!response.ok) {
+ spanNotifaction("Fallo con la respuesta recibida");
+ return false;
+ }
+
+ const result = await response.json();
+
+ const messError = result.error;
+
+ if (messError) {
+ spanNotifaction(messError);
+ return false;
+ }
+
+ spanNotifaction(result.message, false);
+ const addedWords = result.words;
+ WORDS.length = 0;
+ addedWords.forEach((word) => WORDS.push(word));
+ renderWords();
+ return true;
+ } catch (err) {
+ console.error(err);
+ spanNotifaction("Error en la respuesta del servidor");
+ return false;
+ }
+}
+
+async function setUpFormAction() {
+ const saveWords = await sendWordsToSave();
+ if (!saveWords) {
+ return false;
+ }
+
+ const input = FORM_ACTION.querySelector(".action-input");
+ FORM_ACTION.action = "";
+ input.value = "finish_session";
+ FORM_ACTION.submit();
+}
+
+window.addEventListener("DOMContentLoaded", async () => {
+ initWordsFromBox();
+ setupDescribeFormToAddWord();
+
+ const currentPhase = parseInt(
+ document.querySelector(".cts-phase-pf").dataset.phase
+ );
+
+ if (currentPhase == 2) await sendWordsToSave();
+ if (document.querySelector(".cts-content-list-words"))
+ await getListWordsTesters();
+});
diff --git a/tecnicas/static/js/span-notification.js b/tecnicas/static/js/span-notification.js
new file mode 100644
index 0000000000000000000000000000000000000000..086af2c19f0c9fc33f0bb1645ae8b1aa09d0e721
--- /dev/null
+++ b/tecnicas/static/js/span-notification.js
@@ -0,0 +1,15 @@
+function spanNotifaction(messageError, isError = true, time = 4500) {
+ const span = document.createElement("span");
+ span.classList.add("text-xl", "font-bold", "text-white");
+ span.textContent = messageError;
+
+ const div = document.createElement("div");
+ div.classList.add("alert", isError ? "alert-error" : "alert-success");
+ div.appendChild(span);
+
+ document.querySelector(".toast").append(div);
+
+ setTimeout(() => {
+ document.querySelector(".toast").removeChild(div);
+ }, time);
+}
diff --git a/tecnicas/static/js/test-cata.js b/tecnicas/static/js/test-cata.js
new file mode 100644
index 0000000000000000000000000000000000000000..489bbd4474bfa8f7cc8e5b61ece41b4da1800e3d
--- /dev/null
+++ b/tecnicas/static/js/test-cata.js
@@ -0,0 +1,126 @@
+document.addEventListener("DOMContentLoaded", () => {
+ const containFormData = document.querySelector(".words-check-container");
+
+ const form = document.getElementById("wordsForm");
+ const csrfToken = document.querySelector("[name=csrfmiddlewaretoken]").value;
+ const message = document.getElementById("response-message");
+
+ const modal = document.getElementById("confirmModal");
+ const modalContent = document.getElementById("confirmModalContent");
+ const cancelBtn = document.getElementById("cancelBtn");
+ const confirmBtn = document.getElementById("confirmBtn");
+
+ const URL = "/cata/testers/api/ratingword/cata";
+
+ const checkboxes = form.querySelectorAll('input[type="checkbox"]');
+ checkboxes.forEach((cb) => (cb.checked = false));
+
+ let wordsData = [];
+
+ form.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const checkboxes = form.querySelectorAll('input[type="checkbox"]');
+ wordsData = Array.from(checkboxes).map((cb) => ({
+ id: parseInt(cb.dataset.wordId),
+ word: cb.dataset.wordName,
+ is_check: cb.checked,
+ }));
+
+ showModal(wordsData);
+ });
+
+ function showModal(words) {
+ const checkedWords = words.filter((w) => w.is_check);
+ const uncheckedWords = words.filter((w) => !w.is_check);
+
+ modalContent.innerHTML = `
+
+
Palabras seleccionadas:
+
+ ${
+ checkedWords.length > 0
+ ? checkedWords
+ .map(
+ (w) =>
+ `${w.word} `
+ )
+ .join("")
+ : 'Ninguna '
+ }
+
+
+
+
Palabras no seleccionadas:
+
+ ${
+ uncheckedWords.length > 0
+ ? uncheckedWords
+ .map(
+ (w) =>
+ `${w.word} `
+ )
+ .join("")
+ : 'Todas seleccionadas '
+ }
+
+
+ `;
+ modal.classList.remove("hidden");
+ }
+
+ cancelBtn.addEventListener("click", () => {
+ modalContent.innerHTML = "";
+ modal.classList.add("hidden");
+ });
+
+ confirmBtn.addEventListener("click", async () => {
+ modal.classList.add("hidden");
+ const dataProduct = {
+ id: parseInt(document.querySelector(".id-product").textContent),
+ code: document.querySelector(".code-product").textContent,
+ };
+
+ try {
+ const response = await fetch(URL, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "X-CSRFToken": csrfToken,
+ "X-Requested-With": "XMLHttpRequest",
+ },
+ body: JSON.stringify({ words: wordsData, product: dataProduct }),
+ });
+
+ if (!response.ok) {
+ message.textContent = "Error en la respuesta del servidor";
+ message.classList.remove("hidden");
+ }
+
+ const result = await response.json();
+ const messError = result.error;
+
+ if (messError) {
+ message.textContent = messError;
+ return;
+ }
+ containFormData.innerHTML = `
+
+
+ Exito al guardar
+
+
+ ${result.message}
+
+
+ Siguiente Producto
+
+
+ `;
+ } catch (err) {
+ console.error(err);
+ message.textContent = "Error en la respuesta del servidor";
+ message.classList.remove("hidden");
+ }
+ });
+});
diff --git a/tecnicas/static/js/test-napping-plane.js b/tecnicas/static/js/test-napping-plane.js
new file mode 100644
index 0000000000000000000000000000000000000000..ae706fa70bc4d9a8344045f43ea3e781f53a6f67
--- /dev/null
+++ b/tecnicas/static/js/test-napping-plane.js
@@ -0,0 +1,253 @@
+const planeContainer = document.getElementById('napping-plane');
+const productsContainer = document.getElementById('items');
+const products = document.querySelectorAll('.item-product');
+
+// Configuration for the physical dimensions of the tablecloth (in cm)
+const PHYSICAL_WIDTH = 60;
+const PHYSICAL_HEIGHT = 40;
+
+let selectedProductCode = null;
+let selectedProductId = null;
+
+// Object to store coordinates: { "CODE": { x: 10.5, y: 20.1, id: 123 } }
+window.placedPoints = {};
+
+const modeElement = document.querySelector('[data-mode]');
+window.isUltraFlash = modeElement && modeElement.dataset.mode.toLowerCase().includes('ultra flash');
+
+// For ultra flash mode, it starts active but will be controlled by ultra-flash.js
+if (window.isUltraFlash) {
+ document.getElementById("question-save").classList.add("hidden");
+ window.isPlacementActive = true;
+} else {
+ // Normal mode: placement is always active
+ window.isPlacementActive = true;
+}
+
+// 1. Handle Product Selection
+products.forEach(product => {
+ product.addEventListener('click', () => {
+ if (!window.isPlacementActive) return;
+
+ // Remove selection from others
+ products.forEach(p => p.classList.remove('ring-4', 'ring-primary'));
+
+ // Select current
+ product.classList.add('ring-4', 'ring-primary');
+ selectedProductCode = product.dataset.code;
+ selectedProductId = product.dataset.idProduct;
+ });
+});
+
+// 2. Handle Plane Click (Placing Points)
+planeContainer.addEventListener('click', (e) => {
+ if (!window.isPlacementActive) return;
+
+ if (!selectedProductCode) {
+ spanNotifaction("Por favor, selecciona un producto primero")
+ return;
+ }
+
+ const rect = planeContainer.getBoundingClientRect();
+
+ // Calculate click position relative to the container (in pixels)
+ const xPixel = e.clientX - rect.left;
+ const yPixel = e.clientY - rect.top;
+
+ // Calculate scaled coordinates (0 to PHYSICAL_DIMENSIONS)
+ // X axis: 0 on left, 60 on right
+ const xCoord = (xPixel / rect.width) * PHYSICAL_WIDTH;
+
+ // Y axis: 0 on bottom, 40 on top (Invert Y because DOM Y is 0 at top)
+ const yCoord = PHYSICAL_HEIGHT - ((yPixel / rect.height) * PHYSICAL_HEIGHT);
+
+ // Update Data Object
+ window.placedPoints[selectedProductCode] = {
+ x: parseFloat(xCoord.toFixed(2)),
+ y: parseFloat(yCoord.toFixed(2)),
+ id: selectedProductId
+ };
+
+ // Render Point
+ window.renderPoint(selectedProductCode, xPixel, yPixel, xCoord, yCoord);
+});
+
+// Make renderPoint global
+window.renderPoint = function (code, xPx, yPx, xVal, yVal) {
+ // Remove existing point for this product if it exists
+ const existingPoint = document.getElementById(`point-${code}`);
+ if (existingPoint) {
+ existingPoint.remove();
+ }
+
+ const point = document.createElement('div');
+ point.id = `point-${code}`;
+ point.className = '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';
+ point.dataset.code = code;
+ point.dataset.px = xVal;
+ point.dataset.py = yVal;
+ point.dataset.idProduct = window.placedPoints[code]?.id;
+
+ point.style.left = `${xPx}px`;
+ point.style.top = `${yPx}px`;
+
+ planeContainer.appendChild(point);
+
+ const textLabel = document.createElement('span');
+ textLabel.className = 'absolute top-4 left-1/2 transform -translate-x-1/2 text-xs font-bold text-gray-700 pointer-events-none';
+ textLabel.innerText = code;
+ point.appendChild(textLabel);
+}
+
+function checkPoints() {
+ const points = document.querySelectorAll('.data-point');
+
+ points.forEach(point => {
+ const code = point.dataset.code;
+
+ const xVal = parseFloat(point.dataset.px);
+ const yVal = parseFloat(point.dataset.py);
+
+ const rect = planeContainer.getBoundingClientRect();
+
+ const px = (xVal / PHYSICAL_WIDTH) * rect.width;
+ const py = ((PHYSICAL_HEIGHT - yVal) / PHYSICAL_HEIGHT) * rect.height;
+
+ window.placedPoints[code] = {
+ x: xVal.toFixed(2),
+ y: yVal.toFixed(2),
+ id: point.dataset.idProduct
+ };
+
+ window.renderPoint(code, px, py, xVal, yVal);
+ });
+}
+
+setTimeout(checkPoints, 100);
+
+/*
+////
+//////
+//////// Question to finish session
+//////
+////
+*/
+
+function showOptionsSave() {
+ document.getElementById("question-save").classList.add("hidden");
+ document.getElementById("finish-session").classList.remove("hidden");
+ document.getElementById("cancel-save").classList.remove("hidden");
+}
+
+function showQuestionSave() {
+ document.getElementById("question-save").classList.remove("hidden");
+ document.getElementById("finish-session").classList.add("hidden");
+ document.getElementById("cancel-save").classList.add("hidden");
+}
+
+document
+ .getElementById("question-save")
+ .addEventListener("click", showOptionsSave);
+
+document
+ .getElementById("cancel-save")
+ .addEventListener("click", showQuestionSave);
+
+/*
+////
+//////
+//////// Save data and finish session
+//////
+////
+*/
+
+// Callback for additional validation before saving
+window.beforeSaveData = null;
+
+// Function to get extra data for each product
+window.getExtraDataForSave = null;
+
+window.saveData = async function (isFinishSession = false) {
+ const codeProducts = Object.keys(window.placedPoints);
+ const data = [];
+
+ // Only validate all products placed if finishing session
+ if (isFinishSession && products.length != codeProducts.length) {
+ spanNotifaction("Por favor, coloca todos los puntos antes de finalizar la sesión")
+ return false;
+ }
+
+ // Call beforeSaveData callback if it exists (for ultra flash validation)
+ if (window.beforeSaveData && typeof window.beforeSaveData === 'function') {
+ const validationResult = window.beforeSaveData(isFinishSession);
+ if (validationResult === false) {
+ return false;
+ }
+ }
+
+ codeProducts.forEach((code) => {
+ const point = window.placedPoints[code];
+
+ const objData = {
+ code: code,
+ x: point.x,
+ y: point.y,
+ idProduct: point.id
+ };
+
+ // Get extra data if callback exists (for ultra flash words)
+ if (window.getExtraDataForSave && typeof window.getExtraDataForSave === 'function') {
+ const extraData = window.getExtraDataForSave(code);
+ Object.assign(objData, extraData);
+ }
+
+ data.push(objData);
+ })
+
+ const URL = "/cata/testers/api/rating-napping"
+ const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
+
+ try {
+ const response = await fetch(URL, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "X-CSRFToken": csrfToken,
+ },
+ body: JSON.stringify(data),
+ })
+
+ if (!response.ok) {
+ spanNotifaction("Error en la respuesta del servidor")
+ return false;
+ }
+
+ const result = await response.json()
+
+ if (result.error) {
+ spanNotifaction(result.error)
+ return false
+ } else {
+ spanNotifaction(result.message, false)
+ return true
+ }
+ } catch (error) {
+ spanNotifaction("Error en proceso de guardar los datos")
+ return false
+ }
+}
+
+// Function to finish session with validation
+window.finishSession = async function () {
+ const success = await window.saveData(true);
+ if (success) {
+ // Submit the form to finish session
+ const formFinish = document.getElementById("form-finish-session")
+ formFinish.action = ""
+ formFinish.submit();
+ }
+}
+
+document
+ .getElementById("save-progress")
+ .addEventListener("click", () => window.saveData(false));
\ No newline at end of file
diff --git a/tecnicas/static/js/test-napping-sort.js b/tecnicas/static/js/test-napping-sort.js
new file mode 100644
index 0000000000000000000000000000000000000000..bec251888aebad8afbce0426f8724dccf6516e21
--- /dev/null
+++ b/tecnicas/static/js/test-napping-sort.js
@@ -0,0 +1,623 @@
+// Napping Sort Mode - Three Phase Implementation
+// Phase 1: Place products on plane
+// Phase 2: Create groups by selecting points
+// Phase 3: Describe groups with words
+
+// Store groups: { "group-1": ["CODE1", "CODE2"], "group-2": ["CODE3"] }
+const productGroups = {};
+
+// Store group words: { "group-1": ["word1", "word2"], "group-2": ["word3"] }
+const groupWords = {};
+
+// Track which group each product belongs to: { "CODE1": "group-1", "CODE2": "group-1" }
+const productToGroup = {};
+
+// Selected points for creating a group
+let selectedPoints = [];
+
+// Current group counter for generating IDs
+let groupCounter = 1;
+
+// Current phase (1, 2, or 3)
+let currentPhase = 1;
+
+// Current group being described
+let currentGroupId = null;
+
+// Only initialize if in sort mode
+const modeElementSort = document.querySelector('[data-mode]');
+const isSortMode = modeElementSort && modeElementSort.dataset.mode.toLowerCase() === 'sorting';
+
+if (isSortMode) {
+ initSortMode();
+}
+
+function loadExistingGroups() {
+ const dataGroupContainer = document.querySelector('.data-group-products');
+ if (!dataGroupContainer) return { hasGroups: false, hasWords: false };
+
+ const groupElements = dataGroupContainer.querySelectorAll('.item-group');
+ let hasGroups = false;
+ let hasWords = false;
+
+ groupElements.forEach((groupEl, index) => {
+ const groupId = `group-${groupCounter++}`;
+ const products = [];
+ const words = groupEl.dataset.words ? groupEl.dataset.words.split(',').map(w => w.trim()).filter(w => w) : [];
+
+ // Get products in this group
+ groupEl.querySelectorAll('.item-group-product').forEach(productEl => {
+ const code = productEl.dataset.code;
+ products.push(code);
+ productToGroup[code] = groupId;
+ });
+
+ if (products.length > 0) {
+ hasGroups = true;
+ productGroups[groupId] = products;
+ groupWords[groupId] = words;
+
+ if (words.length > 0) {
+ hasWords = true;
+ }
+ }
+ });
+
+ return { hasGroups, hasWords };
+}
+
+function initSortMode() {
+ const continueGroupingBtn = document.getElementById('continue-grouping');
+ const continueDescriptionBtn = document.getElementById('continue-description');
+ const createGroupBtn = document.getElementById('create-group-btn');
+ const dissolveGroupBtn = document.getElementById('dissolve-group-btn');
+ const groupControls = document.getElementById('group-controls');
+ const questionSaveBtn = document.getElementById('question-save');
+
+ const groupWordDialog = document.getElementById('group-word-dialog');
+ const groupWordForm = document.getElementById('group-word-form');
+ const groupWordInput = document.getElementsByName('nombre_palabra')[0];
+ groupWordInput.value = "";
+ const groupWordList = document.getElementById('group-word-list');
+ const dialogGroupId = document.getElementById('dialog-group-id');
+
+ // Hide question save button initially
+ questionSaveBtn.classList.add('hidden');
+
+ // Load existing groups from backend and determine initial phase
+ const { hasGroups, hasWords } = loadExistingGroups();
+
+ setTimeout(() => {
+ // Determine initial phase based on existing data
+ if (hasGroups && hasWords) {
+ // Skip to Phase 3 (Description) if groups have words
+ currentPhase = 3;
+ window.isPlacementActive = false;
+ groupControls.classList.remove('hidden');
+ continueDescriptionBtn.classList.remove('hidden');
+
+ renderExistingGroups();
+ startDescriptionPhase();
+ } else if (hasGroups) {
+ // Skip to Phase 2 (Grouping) if groups exist but no words
+ currentPhase = 2;
+ renderExistingGroups();
+ window.isPlacementActive = false;
+ groupControls.classList.remove('hidden');
+ continueDescriptionBtn.classList.remove('hidden');
+
+ const plane = document.getElementById('napping-plane');
+ plane.classList.remove('cursor-crosshair');
+ plane.classList.add('cursor-default');
+
+ enablePointSelection();
+ spanNotifaction("Continúa agrupando productos o pasa a la descripción.", false);
+ } else {
+ // Start in Phase 1 (Placement)
+ currentPhase = 1;
+ }
+ }, 200);
+
+ // Phase 1: Product Placement
+ // Show continue to grouping button when all products are placed
+ setInterval(() => {
+ if (currentPhase === 1) {
+ const placedCount = Object.keys(window.placedPoints).length;
+ const totalProducts = document.querySelectorAll('.item-product').length;
+
+ if (placedCount === totalProducts && placedCount > 0) {
+ continueGroupingBtn.classList.remove('hidden');
+ } else {
+ continueGroupingBtn.classList.add('hidden');
+ }
+ }
+ }, 500);
+
+ // Transition to Phase 2: Grouping
+ continueGroupingBtn.addEventListener('click', () => {
+ const placedCount = Object.keys(window.placedPoints).length;
+ const totalProducts = document.querySelectorAll('.item-product').length;
+
+ if (placedCount !== totalProducts) {
+ spanNotifaction("Por favor, coloca todos los productos antes de continuar.");
+ return;
+ }
+
+ startGroupingPhase();
+ });
+
+ function startGroupingPhase() {
+ currentPhase = 2;
+ window.isPlacementActive = false;
+ continueGroupingBtn.classList.add('hidden');
+ groupControls.classList.remove('hidden');
+ continueDescriptionBtn.classList.remove('hidden');
+
+ const plane = document.getElementById('napping-plane');
+ plane.classList.remove('cursor-crosshair');
+ plane.classList.add('cursor-default');
+
+ // Remove selection from products
+ document.querySelectorAll('.item-product').forEach(p => {
+ p.classList.remove('ring-4', 'ring-primary');
+ });
+
+ spanNotifaction("Fase de agrupación: Selecciona puntos y crea grupos.", false);
+
+ // Auto-save points when transitioning to Phase 2
+ sortModeSaveData(false);
+
+ // Enable point selection
+ enablePointSelection();
+ }
+
+ function enablePointSelection() {
+ // Add click handler to points for selection
+ document.getElementById('napping-plane').addEventListener('click', (e) => {
+ if (currentPhase !== 2) return;
+
+ const point = e.target.closest('.data-point');
+ if (point) {
+ e.stopPropagation();
+ togglePointSelection(point.dataset.code);
+ }
+ });
+ }
+
+ function togglePointSelection(code) {
+ // Check if point is already in a group
+ if (productToGroup[code]) {
+ spanNotifaction(`El producto ${code} ya pertenece al grupo ${productToGroup[code]}`);
+ return;
+ }
+
+ const point = document.getElementById(`point-${code}`);
+ const index = selectedPoints.indexOf(code);
+
+ if (index > -1) {
+ // Deselect
+ selectedPoints.splice(index, 1);
+ point.classList.remove('ring-4', 'ring-blue-500');
+ point.classList.add('bg-red-600');
+ point.classList.remove('bg-blue-600');
+ } else {
+ // Select
+ selectedPoints.push(code);
+ point.classList.add('ring-4', 'ring-blue-500');
+ point.classList.remove('bg-red-600');
+ point.classList.add('bg-blue-600');
+ }
+ }
+
+ // Create Group
+ createGroupBtn.addEventListener('click', () => {
+ if (selectedPoints.length === 0) {
+ spanNotifaction("Selecciona al menos un punto para crear un grupo");
+ return;
+ }
+
+ const groupId = `group-${groupCounter++}`;
+ productGroups[groupId] = [...selectedPoints];
+ groupWords[groupId] = [];
+
+ // Update product to group mapping
+ selectedPoints.forEach(code => {
+ productToGroup[code] = groupId;
+ });
+
+ // Visual update: change color of grouped points
+ const colors = ['bg-green-600', 'bg-purple-600', 'bg-yellow-600', 'bg-pink-600', 'bg-indigo-600'];
+ const colorIndex = (groupCounter - 2) % colors.length;
+
+ selectedPoints.forEach(code => {
+ const point = document.getElementById(`point-${code}`);
+ point.classList.remove('bg-blue-600', 'ring-4', 'ring-blue-500');
+ point.classList.add(colors[colorIndex]);
+ });
+
+ // Add group to display
+ addGroupToDisplay(groupId, selectedPoints, colors[colorIndex]);
+
+ // Clear selection
+ selectedPoints = [];
+
+ spanNotifaction(`Grupo "${groupId}" creado con éxito`, false);
+ });
+
+ function addGroupToDisplay(groupId, products, colorClass) {
+ const groupsDisplay = document.getElementById('groups-display');
+
+ const groupBadge = document.createElement('div');
+ groupBadge.id = `display-${groupId}`;
+ groupBadge.className = `badge badge-lg gap-2 p-4 ${colorClass} text-white cursor-pointer font-semibold`;
+ groupBadge.innerHTML = `
+ [ ${products.join(', ')} ]
+
+
+
+ `;
+
+ groupsDisplay.appendChild(groupBadge);
+
+ // Add dissolve handler
+ groupBadge.querySelector('.dissolve-group-icon').addEventListener('click', (e) => {
+ e.stopPropagation();
+ dissolveGroup(groupId);
+ });
+
+ // Add click handler for Phase 3 (describe group)
+ groupBadge.addEventListener('click', () => {
+ if (currentPhase === 3) {
+ openGroupWordDialog(groupId);
+ }
+ });
+ }
+
+ function dissolveGroup(groupId) {
+ if (currentPhase === 3) {
+ spanNotifaction("No puedes disolver un grupo en la fase de descripción");
+ return;
+ }
+
+ if (groupWords[groupId] && groupWords[groupId].length > 0) {
+ spanNotifaction("No puedes disolver un grupo que ya tiene palabras descriptivas");
+ return;
+ }
+
+ // Remove from product to group mapping
+ productGroups[groupId].forEach(code => {
+ delete productToGroup[code];
+ const point = document.getElementById(`point-${code}`);
+ point.classList.remove('bg-green-600', 'bg-purple-600', 'bg-yellow-600', 'bg-pink-600', 'bg-indigo-600');
+ point.classList.add('bg-red-600');
+ });
+
+ // Remove group
+ delete productGroups[groupId];
+ delete groupWords[groupId];
+
+ // Remove from display
+ const displayElement = document.getElementById(`display-${groupId}`);
+ if (displayElement) {
+ displayElement.remove();
+ }
+
+ spanNotifaction(`Grupo "${groupId}" disuelto`, false);
+ }
+
+ // Dissolve Group button (dissolves last created group or selected group)
+ dissolveGroupBtn.addEventListener('click', () => {
+ const groupIds = Object.keys(productGroups);
+ if (groupIds.length === 0) {
+ spanNotifaction("No hay grupos para disolver");
+ return;
+ }
+
+ // Dissolve the last group
+ const lastGroupId = groupIds[groupIds.length - 1];
+ dissolveGroup(lastGroupId);
+ });
+
+ // Transition to Phase 3: Description
+ continueDescriptionBtn.addEventListener('click', () => {
+ const groupIds = Object.keys(productGroups);
+ if (groupIds.length === 0) {
+ spanNotifaction("Crea al menos un grupo para continuar");
+ return;
+ }
+
+ // Check all products are in groups
+ const totalProducts = document.querySelectorAll('.item-product').length;
+ const groupedProducts = Object.keys(productToGroup).length;
+
+ if (groupedProducts !== totalProducts) {
+ spanNotifaction("Todos los productos deben estar asignados a un grupo");
+ return;
+ }
+
+ startDescriptionPhase();
+ });
+
+ function startDescriptionPhase() {
+ currentPhase = 3;
+ continueDescriptionBtn.classList.add('hidden');
+
+ createGroupBtn.classList.add('hidden');
+ dissolveGroupBtn.classList.add('hidden');
+
+ questionSaveBtn.classList.remove('hidden');
+
+ const iconsDisolveGroup = document.querySelectorAll('.dissolve-group-icon');
+ for (let index = 0; index < iconsDisolveGroup.length; index++) {
+ const icon = iconsDisolveGroup.item(index);
+ icon.remove();
+ }
+
+ spanNotifaction("Fase de descripción: Haz clic en un grupo para agregar palabras.", false);
+
+ // Auto-save groups when transitioning to Phase 3
+ sortModeSaveData(false);
+ }
+
+ // Group Word Dialog Functions
+ function openGroupWordDialog(groupId) {
+ currentGroupId = groupId;
+ dialogGroupId.innerText = groupId;
+ renderGroupWordList();
+ groupWordDialog.showModal();
+ }
+
+ function renderGroupWordList() {
+ groupWordList.innerHTML = '';
+ const words = groupWords[currentGroupId] || [];
+
+ words.forEach((word, index) => {
+ const badge = document.createElement('div');
+ badge.className = 'badge badge-secondary gap-2 p-3';
+ badge.innerHTML = `
+ ${word}
+
+ `;
+
+ badge.querySelector('.remove-word').addEventListener('click', () => {
+ removeGroupWord(index);
+ });
+
+ groupWordList.appendChild(badge);
+
+ });
+
+ // Update group display with word count
+ updateGroupDisplayWithWords(currentGroupId);
+ }
+
+ function addGroupWord(word) {
+ if (!groupWords[currentGroupId]) {
+ groupWords[currentGroupId] = [];
+ }
+
+ if (groupWords[currentGroupId].includes(word)) {
+ spanNotifaction("Palabra duplicada");
+ return;
+ }
+
+ groupWords[currentGroupId].push(word);
+ renderGroupWordList();
+ }
+
+ function removeGroupWord(index) {
+ if (groupWords[currentGroupId]) {
+ groupWords[currentGroupId].splice(index, 1);
+ renderGroupWordList();
+ }
+ }
+
+ groupWordForm.addEventListener('submit', (e) => {
+ e.preventDefault();
+ const word = groupWordInput.value.trim();
+ if (word) {
+ addGroupWord(word);
+ groupWordInput.value = '';
+ groupWordInput.focus();
+ }
+ });
+
+ function updateGroupDisplayWithWords(groupId) {
+ const displayElement = document.getElementById(`display-${groupId}`);
+ if (!displayElement) return;
+
+ const words = groupWords[groupId] || [];
+ const products = productGroups[groupId] || [];
+
+ // Add tooltip with words on hover
+ if (words.length > 0) {
+ let tooltip = displayElement.querySelector('.group-tooltip');
+ if (!tooltip) {
+ tooltip = document.createElement('div');
+ tooltip.className = 'group-tooltip absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-2 bg-gray-800 text-white text-xs rounded z-10 opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none hidden';
+ displayElement.classList.add('group', 'relative');
+ displayElement.appendChild(tooltip);
+ }
+
+ const wordBadges = words.map(w => `${w} `).join('');
+ tooltip.innerHTML = `
+ ${groupId}
+
+ ${wordBadges}
+
+ `;
+ tooltip.style.maxWidth = '320px';
+ tooltip.style.whiteSpace = 'normal';
+ tooltip.classList.remove('hidden');
+ } else {
+ let tooltip = displayElement.querySelector('.group-tooltip');
+ if (tooltip) {
+ tooltip.remove();
+ }
+ }
+ }
+
+ function renderExistingGroups() {
+ const colors = ['bg-green-600', 'bg-purple-600', 'bg-yellow-600', 'bg-pink-600', 'bg-indigo-600'];
+ let colorIndex = 0;
+
+ for (const [groupId, products] of Object.entries(productGroups)) {
+ const color = colors[colorIndex % colors.length];
+ colorIndex++;
+
+ // Update point colors
+ products.forEach(code => {
+ const point = document.getElementById(`point-${code}`);
+
+ if (point) {
+ point.classList.remove('bg-red-600');
+ point.classList.add(color);
+ }
+ });
+
+ // Add group to display
+ addGroupToDisplay(groupId, products, color);
+
+ // Update display with words if they exist
+ if (groupWords[groupId] && groupWords[groupId].length > 0) {
+ updateGroupDisplayWithWords(groupId);
+ }
+ }
+ }
+
+ // Set up callbacks to extend the base saveData function
+ window.beforeSaveData = function (isFinishSession = false) {
+ if (isFinishSession) {
+ // Validate all products are placed
+ const totalProducts = document.querySelectorAll('.item-product').length;
+ const placedCount = Object.keys(window.placedPoints).length;
+
+ if (placedCount !== totalProducts) {
+ spanNotifaction("Por favor, coloca todos los productos antes de finalizar la sesión");
+ return false;
+ }
+
+ // Validate all products are in groups
+ const groupedProducts = Object.keys(productToGroup).length;
+ if (groupedProducts !== totalProducts) {
+ spanNotifaction("Todos los productos deben estar asignados a un grupo");
+ return false;
+ }
+
+ // Validate each group has at least one product (already guaranteed by creation logic)
+ const groupIds = Object.keys(productGroups);
+ if (groupIds.length === 0) {
+ spanNotifaction("Debe existir al menos un grupo");
+ return false;
+ }
+
+ // Validate each group has at least one word
+ for (const groupId of groupIds) {
+ const words = groupWords[groupId] || [];
+ if (words.length < 1) {
+ spanNotifaction(`El grupo ${groupId} debe tener al menos una palabra descriptiva`);
+ return false;
+ }
+ }
+ }
+ return true;
+ };
+
+ // Override save-progress button to use sort mode save function
+ const saveProgressBtn = document.getElementById('save-progress');
+ // Remove existing event listener by cloning and replacing
+ const newSaveProgressBtn = saveProgressBtn.cloneNode(true);
+ newSaveProgressBtn.textContent = 'Guardar Progreso Sort';
+ saveProgressBtn.parentNode.replaceChild(newSaveProgressBtn, saveProgressBtn);
+
+ newSaveProgressBtn.addEventListener('click', async () => {
+ await sortModeSaveData(false);
+ });
+
+ // Override finish-session button to use sort mode save function
+ document.getElementById('finish-session').addEventListener('click', async (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const success = await sortModeSaveData(true);
+ if (success) {
+ const formFinish = document.getElementById("form-finish-session");
+ formFinish.action = "";
+ formFinish.submit();
+ }
+ });
+
+ // Sort mode specific save function
+ async function sortModeSaveData(isFinishSession = false) {
+ // Run validation callback if it exists
+ if (window.beforeSaveData && typeof window.beforeSaveData === 'function') {
+ const validationResult = window.beforeSaveData(isFinishSession);
+ if (validationResult === false) {
+ return false;
+ }
+ }
+
+ // Build products array with basic position info and group assignment
+ const products = [];
+ for (const [code, point] of Object.entries(window.placedPoints)) {
+ const groupId = productToGroup[code];
+ products.push({
+ code: code,
+ x: point.x,
+ y: point.y,
+ idProduct: point.id,
+ group: groupId || "" // Empty string if no group assigned
+ });
+ }
+
+ // Build groups object with word arrays
+ const groups = {};
+ const groupIds = Object.keys(productGroups);
+
+ // Only include groups if they exist
+ if (groupIds.length > 0) {
+ for (const groupId of groupIds) {
+ groups[groupId] = groupWords[groupId] || [];
+ }
+ }
+
+ // Build the data structure
+ const data = { products: products };
+
+ // Only add groups if they exist
+ if (Object.keys(groups).length > 0) {
+ data.groups = groups;
+ }
+
+ const URL = "/cata/testers/api/rating-napping";
+ const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
+
+ try {
+ const response = await fetch(URL, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "X-CSRFToken": csrfToken,
+ },
+ body: JSON.stringify(data),
+ });
+
+ if (!response.ok) {
+ spanNotifaction("Error en la respuesta del servidor");
+ return false;
+ }
+
+ const result = await response.json();
+
+ if (result.error) {
+ spanNotifaction(result.error);
+ return false;
+ } else {
+ spanNotifaction(result.message, false);
+ return true;
+ }
+ } catch (error) {
+ spanNotifaction("Error en proceso de guardar los datos");
+ return false;
+ }
+ }
+}
diff --git a/tecnicas/static/js/test-napping-ultra-flash.js b/tecnicas/static/js/test-napping-ultra-flash.js
new file mode 100644
index 0000000000000000000000000000000000000000..7b8988000980e3e52f99abf8f5b9f0233fc130ad
--- /dev/null
+++ b/tecnicas/static/js/test-napping-ultra-flash.js
@@ -0,0 +1,235 @@
+// Store words: { "CODE": ["word1", "word2"] }
+const productWords = {};
+
+// Only initialize ultra flash if the mode is active
+if (window.isUltraFlash) {
+ initUltraFlash();
+}
+
+function initUltraFlash() {
+ const continueBtn = document.getElementById('continue-description');
+ const questionSaveBtn = document.getElementById('question-save');
+ const dialog = document.getElementById('word-dialog');
+ const wordForm = document.getElementById('word-form');
+ const wordInput = document.querySelector('.cts-input-list-word');
+ const wordList = document.getElementById('word-list');
+ const dialogProductCode = document.getElementById('dialog-product-code');
+
+ let isDescriptionPhase = false;
+ let currentProductCode = null;
+
+ const points = document.querySelectorAll('.data-point');
+ let hasExistingWords = false;
+
+ wordInput.value = '';
+
+ // Check if there are existing words from backend
+ points.forEach(point => {
+ const code = point.dataset.code;
+ const wordsAttr = point.dataset.words;
+
+ if (wordsAttr && wordsAttr.trim() !== '') {
+ productWords[code] = wordsAttr.split(',').filter(w => w.trim() !== '');
+
+ if (productWords[code].length >= 1) {
+ hasExistingWords = true;
+ }
+ }
+ });
+
+ setTimeout(() => {
+ if (hasExistingWords) {
+ startDescriptionPhase();
+ // Update all point labels to show existing words
+ points.forEach(point => {
+ const code = point.dataset.code;
+ if (productWords[code] && productWords[code].length > 0) {
+ updatePointLabel(code);
+ }
+ });
+ } else {
+ // No existing words, show continue button for phase 1
+ continueBtn.classList.remove('hidden');
+ }
+ }, 100);
+
+ // Check if all products are placed
+ continueBtn.addEventListener('click', () => {
+ const placedCount = Object.keys(window.placedPoints).length;
+ const totalProducts = document.querySelectorAll('.item-product').length;
+
+ if (placedCount !== totalProducts) {
+ spanNotifaction("Por favor, coloca todos los productos antes de continuar.");
+ return;
+ }
+
+ startDescriptionPhase();
+ });
+
+ function startDescriptionPhase() {
+ isDescriptionPhase = true;
+ window.isPlacementActive = false;
+ continueBtn.classList.add('hidden');
+ questionSaveBtn.classList.remove("hidden");
+ spanNotifaction("Fase de descripción: Haz clic en un punto para agregar palabras.", false);
+
+ const plane = document.getElementById('napping-plane');
+ plane.classList.remove('cursor-crosshair');
+ plane.classList.add('cursor-default');
+ document.querySelectorAll('.item-product').forEach(p => {
+ p.classList.remove('ring-4', 'ring-primary');
+ });
+
+ // Auto-save positions when transitioning to description phase
+ window.saveData(false);
+ }
+
+ // Handle Point Click for Description
+ // We need to attach this to the plane or points.
+ // Since points are re-rendered, delegating to plane is better, or hooking into renderPoint.
+ // But renderPoint is in the other file.
+ // Let's use event delegation on the plane, but we need to catch the click on the point.
+
+ document.getElementById('napping-plane').addEventListener('click', (e) => {
+ if (!isDescriptionPhase) return;
+
+ const point = e.target.closest('.data-point');
+
+ if (point) {
+ e.stopPropagation();
+ openWordDialog(point.dataset.code);
+ }
+ });
+
+ function openWordDialog(code) {
+ currentProductCode = code;
+ dialogProductCode.innerText = code;
+ renderWordListInDialog();
+ dialog.showModal();
+ }
+
+ function renderWordListInDialog() {
+ wordList.innerHTML = '';
+ const words = productWords[currentProductCode] || [];
+
+ words.forEach((word, index) => {
+ const badge = document.createElement('div');
+ badge.className = 'badge badge-secondary gap-2 p-3';
+ badge.innerHTML = `
+ ${word}
+
+ `;
+
+ badge.querySelector('.remove-word').addEventListener('click', () => {
+ removeWord(index);
+ });
+
+ wordList.appendChild(badge);
+ });
+
+ // Update visualization on the plane
+ updatePointLabel(currentProductCode);
+ }
+
+ function addWord(word) {
+ if (!productWords[currentProductCode]) {
+ productWords[currentProductCode] = [];
+ }
+
+ // No maximum limit on words
+
+ if (productWords[currentProductCode].includes(word)) {
+ spanNotifaction("Palabra duplicada");
+ return;
+ }
+
+ productWords[currentProductCode].push(word);
+ renderWordListInDialog();
+ }
+
+ function removeWord(index) {
+ if (productWords[currentProductCode]) {
+ productWords[currentProductCode].splice(index, 1);
+ renderWordListInDialog();
+ }
+ }
+
+ wordForm.addEventListener('submit', (e) => {
+ e.preventDefault();
+ const word = wordInput.value.trim();
+ if (word) {
+ addWord(word);
+ wordInput.value = '';
+ wordInput.focus();
+ }
+ });
+
+ function updatePointLabel(code) {
+ const point = document.getElementById(`point-${code}`);
+ if (!point) return;
+
+ const words = productWords[code] || [];
+
+ // Remove existing tooltip if present
+ let tooltip = point.querySelector('.cts-tooltip');
+
+ if (tooltip) {
+ tooltip.remove();
+ }
+
+ // Only create tooltip in description phase and if there are words
+ if (isDescriptionPhase && words.length > 0) {
+ tooltip = document.createElement('div');
+ tooltip.className = 'cts-tooltip absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-2 bg-gray-800 text-white text-xs rounded z-10 opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none';
+
+ // Display words in a 3-column grid (no coordinates)
+ const wordBadges = words.map(w => `${w} `).join('');
+ tooltip.innerHTML = `
+ ${code}
+
+ ${wordBadges}
+
+ `;
+
+ // Add max-width to tooltip
+ tooltip.style.maxWidth = '320px';
+ tooltip.style.whiteSpace = 'normal';
+ point.appendChild(tooltip);
+ }
+ }
+
+ // Set up callbacks to extend the base saveData function
+ // Validation callback - runs before saving
+ window.beforeSaveData = function (isFinishSession = false) {
+ // If finishing session, validate all products placed and have words
+ if (isFinishSession) {
+ const totalProducts = document.querySelectorAll('.item-product').length;
+ const codeProducts = Object.keys(window.placedPoints);
+
+ // Check all products are placed
+ if (codeProducts.length !== totalProducts) {
+ spanNotifaction("Por favor, coloca todos los productos antes de finalizar la sesión.");
+ return false;
+ }
+
+ // Check each product has at least 1 word
+ for (const code of codeProducts) {
+ const words = productWords[code] || [];
+ if (words.length < 1) {
+ spanNotifaction(`El producto ${code} debe tener al menos 1 palabra para finalizar la sesión.`);
+ return false;
+ }
+ }
+ }
+ // For progress save, no validation needed
+ return true;
+ };
+
+ // Data extension callback - adds words to each product's data
+ window.getExtraDataForSave = function (code) {
+ const words = productWords[code] || [];
+ return {
+ words: words
+ };
+ };
+}
diff --git a/tecnicas/static/js/test-sort.js b/tecnicas/static/js/test-sort.js
new file mode 100644
index 0000000000000000000000000000000000000000..0d46731c1b35bc5d8c3e4d6627a5267db9579347
--- /dev/null
+++ b/tecnicas/static/js/test-sort.js
@@ -0,0 +1,554 @@
+let dragged = null;
+
+const productsPlaceHolder = `
+
+ Agrupación de productos
+
`;
+
+const wordsPlaceHolder = `
+
+ Lista de atributos
+
`;
+
+const products = document.querySelectorAll(".draggable");
+const zonesDrop = document.querySelectorAll(".dropzone");
+
+const DATA_GRUPS = {};
+
+products.forEach(manageDragables);
+zonesDrop.forEach(manageDropZone);
+
+
+/*
+////
+//////
+//////// Add new grups
+//////
+////
+*/
+
+function addNewGrup() {
+ const container = document.getElementById("containers");
+ const newGrup = document.createElement("section");
+ const styles = "w-fit space-y-4 border rounded p-2 bg-surface-sweet";
+ newGrup.classList.add(...styles.split(" "));
+
+ const formWord = document.getElementById("form-word");
+ const formWordClone = formWord.cloneNode(true);
+ formWordClone.classList.remove("hidden");
+
+ newGrup.innerHTML = `
+
+ ${productsPlaceHolder}
+
+ ${formWordClone.outerHTML}
+
+ ${wordsPlaceHolder}
+
+
+
+ ¿Remover este grupo?
+
+
+ Remover
+
+
+ Conservar
+
+
`;
+
+ manageDropZone(newGrup.querySelector(".dropzone"));
+ container.appendChild(newGrup);
+}
+
+
+/*
+////
+//////
+//////// Manage products' drags and remove drags
+//////
+////
+*/
+
+/**
+ *
+ * @param {HTMLElement} zone
+ */
+function manageDropZone(zone) {
+ /*
+ ////
+ ////// Trash Zone product
+ ////
+ */
+ if (zone.classList.contains("trash-products")) {
+ zone.addEventListener("dragover", (e) => e.preventDefault());
+
+ zone.addEventListener("drop", (event) => {
+ const codeDrag = dragged.dataset.code
+ const oldParent = dragged.parentNode;
+ const oldCodeZone = oldParent.getAttribute("id")
+
+ if (oldParent.classList.contains("original-products")) {
+ spanNotifaction("No puedes borrar este producto");
+ return
+ }
+
+ dragged.remove()
+ removeProduct(codeDrag, oldCodeZone)
+
+ spanNotifaction("Producto removido del grupo", false);
+ dragged = null
+
+ if (oldParent.classList.contains("dropzone") && oldParent.children.length === 0) {
+ oldParent.innerHTML = productsPlaceHolder;
+ }
+ })
+ return
+ }
+
+ /*
+ ////
+ ////// Identificate grups and creata dinamic data
+ ////
+ */
+
+ // Create id for grup
+ const idZone = generateSimpleID()
+
+ // Add products if there en the page
+ const products = []
+ const productsElements = zone.querySelectorAll(".draggable") || None;
+
+ if (productsElements) {
+ productsElements.forEach((proEle) => {
+ products.push({
+ id: proEle.dataset.idProduct,
+ code: proEle.dataset.code
+ })
+ })
+ }
+
+ const parentZone = zone.parentNode;
+ const wordsInGrup = parentZone.querySelectorAll(".item-word") || None
+
+ // Add words if there en the page
+ const words = []
+ if (wordsInGrup) {
+ wordsInGrup.forEach((wordElement) => {
+ words.push(wordElement.textContent)
+ })
+ }
+
+ DATA_GRUPS[idZone] = {
+ products: products,
+ words: words
+ }
+
+ /*
+ ////
+ ////// Drop Zone product
+ ////
+ */
+
+ zone.setAttribute("id", idZone)
+ zone.addEventListener("dragover", (e) => e.preventDefault());
+
+ zone.addEventListener("drop", () => {
+ const codeDrag = dragged.dataset.code
+ const zoneCode = zone.getAttribute("id")
+
+ // Check that new produck is not in currents products of the group
+ const obj = DATA_GRUPS[zoneCode].products.find((product) => product.code == codeDrag)
+ if (obj) {
+ spanNotifaction("Ese producto ya está en el grupo");
+ return
+ }
+
+ DATA_GRUPS[zoneCode].products.push({
+ id: dragged.dataset.idProduct,
+ code: dragged.dataset.code
+ })
+
+ const oldParent = dragged.parentNode;
+
+ const p = zone.querySelector("p");
+ if (p) {
+ p.remove();
+ }
+
+ // Move original or clone product in zone
+ if (oldParent.classList.contains("dropzone")) {
+ zone.appendChild(dragged);
+ } else {
+ const clone = dragged.cloneNode(true);
+ clone.classList.remove("opacity-50");
+ manageDragables(clone);
+ zone.appendChild(clone);
+ }
+
+ if (oldParent.classList.contains("dropzone")) {
+ if (oldParent.children.length === 0) oldParent.innerHTML = productsPlaceHolder;
+ const zoneCodeOld = oldParent.getAttribute("id")
+ removeProduct(codeDrag, zoneCodeOld)
+ }
+ });
+
+ // Add listeners to elements options and WordForm for the list words
+ addListenersButtonQuestionGrup(zone.parentNode)
+ manageFormWord(parentZone.querySelector("form"), parentZone.querySelector(".words-container"), idZone)
+
+ if (DATA_GRUPS[idZone].words.length) {
+ parentZone.querySelector(".words-container").innerHTML = "";
+ DATA_GRUPS[idZone].words.forEach((word) => {
+ const itemWord = getItemWord(word, idZone);
+ parentZone.querySelector(".words-container").appendChild(itemWord);
+ })
+ }
+}
+
+/*
+////
+//////
+//////// Management Drags and FormWord
+//////
+////
+*/
+
+/**
+ *
+ * @param {HTMLElement} el
+ */
+function manageDragables(el) {
+ el.addEventListener("dragstart", () => {
+ dragged = el;
+ setTimeout(() => el.classList.add("opacity-50"), 0);
+ });
+
+ el.addEventListener("dragend", () => {
+ dragged = null;
+ el.classList.remove("opacity-50");
+ });
+}
+
+/**
+ *
+ * @param {HTMLFormElementt} form
+ * @param {HTMLElement} containerWords
+ */
+function manageFormWord(form, containerWords, codeGrup) {
+ form.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ if (!form.reportValidity()) {
+ return;
+ }
+
+ const input = form.querySelector('input[type="text"]');
+
+ if (!input) return;
+
+ const name = input.value.trim();
+ if (!name) return;
+
+ if (DATA_GRUPS[codeGrup].words.includes(name)) {
+ input.value = "";
+ input.focus();
+ spanNotifaction("Esa palabra ya está en la lista");
+ return;
+ }
+
+ DATA_GRUPS[codeGrup].words.push(name)
+ const wordItem = getItemWord(name, codeGrup)
+
+ const p = containerWords.querySelector("p");
+ if (p) {
+ p.remove();
+ }
+
+ containerWords.appendChild(wordItem)
+
+ input.value = "";
+ input.focus();
+ });
+}
+
+/*
+////
+//////
+//////// Management Drags and FormWord
+//////
+////
+*/
+
+/**
+ *
+ * @param {HTMLElement} grup
+ */
+function removeGrup(grup) {
+ delete DATA_GRUPS[grup.querySelector(".dropzone").getAttribute("id")]
+ grup.remove()
+}
+
+function removeWord(code, wordName) {
+ const newWords = DATA_GRUPS[code].words.filter(word => word != wordName)
+ DATA_GRUPS[code].words = newWords
+ if (!newWords.length) {
+ const containerWords = document.getElementById(code).parentNode.querySelector(".words-container")
+ containerWords.innerHTML = wordsPlaceHolder;
+ }
+}
+
+function removeProduct(codeProduct, codeZone) {
+ const newProducts = DATA_GRUPS[codeZone].products.filter((product) => product.code != codeProduct)
+ DATA_GRUPS[codeZone].products = newProducts
+}
+
+function getItemWord(wordName, code) {
+ const STYLES_DIV = "cts-item-words bg-surface-ligt text-black rounded font-bold text-lg p-1 flex flex-wrap flex-row flex-1 min-w-fit justify-center items-center gap-3";
+ const STYLES_BTN = "cts-remove-word cts-btn-general-compress px-2 cts-btn-error"
+
+ const btn = document.createElement("button");
+ btn.setAttribute("data-code", code);
+ btn.setAttribute("data-word", wordName);
+ btn.classList.add(...STYLES_BTN.split(" "));
+ btn.textContent = "➖";
+
+ const span = document.createElement("span");
+ span.classList.add("item-word");
+ span.textContent = wordName;
+
+ const div = document.createElement("div");
+ div.setAttribute("id", `word-${code}-${wordName}`);
+ div.classList.add(...STYLES_DIV.split(" "));
+
+ div.appendChild(span);
+ div.appendChild(btn);
+
+ btn.addEventListener("click", (e) => {
+ removeWord(code, wordName)
+ div.remove()
+ })
+
+ return div;
+};
+
+/**
+ *
+ * @param {HTMLElement} grupContainer
+ */
+function addListenersButtonQuestionGrup(grupContainer) {
+ // Function hidden question
+ grupContainer.querySelector(".cts-question").addEventListener("click", (event) => {
+ hiddenQuestionRemoveGroup(grupContainer)
+ })
+
+ // Function cancel remove
+ grupContainer.querySelector(".cts-no-remove").addEventListener("click", (event) => {
+ hiddenQuestionRemoveGroup(grupContainer, false)
+ })
+
+ // Function remove grup
+ grupContainer.querySelector(".cts-remove").addEventListener("click", (event) => {
+ removeGrup(grupContainer)
+ })
+}
+
+/*
+////
+//////
+//////// Management Options Save and remove groups
+//////
+////
+*/
+
+/**
+ *
+ * @param {HTMLDivElement} container
+ * @param {boolean} hiddenQuestion
+ */
+function hiddenQuestionRemoveGroup(container, hiddenQuestion = true) {
+ const question = container.querySelector(".cts-question")
+ const remove = container.querySelector(".cts-remove")
+ const noRemove = container.querySelector(".cts-no-remove")
+
+
+ if (hiddenQuestion) {
+ question.classList.add("hidden")
+ remove.classList.remove("hidden")
+ noRemove.classList.remove("hidden")
+ } else {
+ question.classList.remove("hidden")
+ remove.classList.add("hidden")
+ noRemove.classList.add("hidden")
+ }
+}
+
+function generateSimpleID() {
+ const first = Date.now().toString(35);
+ const second = Math.random().toString(36).slice(2);
+ return first + second;
+}
+
+function showOptionsSave() {
+ document.getElementById("question-save").classList.add("hidden");
+ document.getElementById("finish-session").classList.remove("hidden");
+ document.getElementById("cancel-save").classList.remove("hidden");
+}
+
+function showQuestionSave() {
+ document.getElementById("question-save").classList.remove("hidden");
+ document.getElementById("finish-session").classList.add("hidden");
+ document.getElementById("cancel-save").classList.add("hidden");
+}
+
+document
+ .getElementById("question-save")
+ .addEventListener("click", showOptionsSave);
+
+document
+ .getElementById("cancel-save")
+ .addEventListener("click", showQuestionSave);
+
+/*
+////
+//////
+//////// Save data
+//////
+////
+*/
+
+async function saveData() {
+ const keysDataGrups = Object.keys(DATA_GRUPS);
+ const allCodesProducts = new Set()
+
+ products.forEach((productElement) => {
+ allCodesProducts.add(productElement.getAttribute("data-code"))
+ })
+
+ if (keysDataGrups.length === 0) {
+ spanNotifaction("No hay grupos para guardar")
+ return false;
+ }
+
+ const data = []
+ let thereError = false;
+ const codesProducts = []
+
+ keysDataGrups.forEach((key) => {
+ if (thereError) return
+
+ const dataGrup = DATA_GRUPS[key];
+ if (!dataGrup) {
+ spanNotifaction("No hay datos para guardar")
+ thereError = true;
+ return
+ }
+
+ if (dataGrup.products.length === 0) {
+ spanNotifaction("Los grupos deben tener por lo menos un producto para guardar")
+ thereError = true;
+ return
+ }
+
+ if (dataGrup.words.length === 0) {
+ spanNotifaction("Los grupos deben tener por lo menos una palabra para guardar")
+ thereError = true;
+ return
+ }
+
+ const words = dataGrup.words;
+ const products = dataGrup.products;
+
+ codesProducts.push(...products.map((product) => product.code))
+
+ data.push({
+ words,
+ products
+ })
+ })
+
+ if (thereError) return false;
+
+ const currentCodesProducts = new Set(codesProducts)
+ const difference = symmetricDifference(currentCodesProducts, allCodesProducts)
+
+ if (difference.size > 0) {
+ spanNotifaction("Falta un producto que debe ser ordenado")
+ return false
+ }
+
+ const URL = "/cata/testers/api/rating-sort"
+ const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
+
+ try {
+ const response = await fetch(URL, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "X-CSRFToken": csrfToken,
+ },
+ body: JSON.stringify(data),
+ })
+
+ if (!response.ok) {
+ spanNotifaction("Error en la respuesta del servidor")
+ return false;
+ }
+
+ const result = await response.json()
+
+ if (result.error) {
+ spanNotifaction(result.error)
+ return false
+ } else {
+ spanNotifaction(result.message, false)
+ return true
+ }
+ } catch (error) {
+ spanNotifaction("Error en proceso de guardar los datos")
+ return false
+ }
+}
+
+const buttonSaveData = document.getElementById("save-progress")
+buttonSaveData.addEventListener("click", saveData)
+
+function symmetricDifference(setA, setB) {
+ let _difference = new Set(setA);
+ for (let elem of setB) {
+ if (_difference.has(elem)) {
+ _difference.delete(elem);
+ } else {
+ _difference.add(elem);
+ }
+ }
+ return _difference;
+}
+
+/*
+////
+//////
+//////// Finish session
+//////
+////
+*/
+
+document
+ .getElementById("finish-session")
+ .addEventListener("click", finishSession);
+
+async function finishSession() {
+ const save = await saveData();
+ if (!save) {
+ spanNotifaction("Error al guardar los datos")
+ return
+ };
+
+ const FORM_ACTION = document.querySelector(".form-actions")
+
+ const inputAction = FORM_ACTION.querySelector(".action-input");
+ FORM_ACTION.action = "";
+ inputAction.value = "finish_session";
+ FORM_ACTION.submit();
+}
\ No newline at end of file
diff --git a/tecnicas/static/js/test_pf_rating_list.js b/tecnicas/static/js/test_pf_rating_list.js
new file mode 100644
index 0000000000000000000000000000000000000000..08786f95639e7b83568df0cc97b31121f7c9322d
--- /dev/null
+++ b/tecnicas/static/js/test_pf_rating_list.js
@@ -0,0 +1,143 @@
+let dragged = null;
+const nextProduct = `
+Éxito al guardar los datos
+
+
+ Evaluar siguiente atributo
+
+
`;
+
+document.querySelectorAll(".draggable").forEach((el) => {
+ el.addEventListener("dragstart", () => {
+ dragged = el;
+ setTimeout(() => el.classList.add("opacity-50"), 0);
+ });
+
+ el.addEventListener("dragend", () => {
+ dragged = null;
+ el.classList.remove("opacity-50");
+ });
+});
+
+document.querySelectorAll(".dropzone").forEach((zone) => {
+ zone.addEventListener("dragover", (e) => e.preventDefault());
+
+ zone.addEventListener("drop", () => {
+ zone.appendChild(dragged);
+ });
+});
+
+document
+ .getElementById("question-save")
+ .addEventListener("click", showOptionsSave);
+
+document
+ .getElementById("cancel-save")
+ .addEventListener("click", showQuestionSave);
+
+document.getElementById("save-data").addEventListener("click", async () => {
+ showLoading();
+ const currentDataRatend = [];
+
+ document.querySelectorAll(".dropzone").forEach((zone) => {
+ const index = parseInt(zone.dataset.index);
+ const children = zone.querySelectorAll(".draggable");
+
+ children.forEach((el) => {
+ currentDataRatend.push({
+ product: {
+ id: el.dataset.idProduct,
+ code: el.dataset.code,
+ },
+ value: index,
+ });
+ });
+ });
+
+ if (!currentDataRatend.length) {
+ cancelLoading();
+ spanNotifaction("No has ordenado los productos");
+ } else if (
+ currentDataRatend.length != document.querySelectorAll(".draggable").length
+ ) {
+ cancelLoading();
+ spanNotifaction("Faltan productos por calificar");
+ } else {
+ saveData(currentDataRatend);
+ }
+});
+
+async function saveData(dataToSend = []) {
+ const URL = "/cata/testers/api/ratingword/pf/list";
+
+ const csrfToken = document.querySelector("[name=csrfmiddlewaretoken]").value;
+
+ const requestData = {
+ phase: parseInt(document.querySelector(".cts-phase-pf").dataset.phase),
+ word: document.querySelector(".cts-word-rating").dataset.nameWord,
+ data: dataToSend,
+ };
+
+ try {
+ const response = await fetch(URL, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "X-CSRFToken": csrfToken,
+ "X-Requested-With": "XMLHttpRequest",
+ },
+ body: JSON.stringify(requestData),
+ });
+
+ if (!response.ok) {
+ spanNotifaction("Fallo con la respuesta recibida");
+ cancelLoading();
+ return false;
+ }
+
+ const result = await response.json();
+
+ const messError = result.error;
+
+ if (messError) {
+ spanNotifaction(messError);
+ cancelLoading();
+ return false;
+ }
+
+ spanNotifaction(result.message, false);
+ const containerRatings = document.querySelector(".container-rating-word");
+ containerRatings.innerHTML = "";
+ containerRatings.innerHTML = nextProduct;
+ cancelLoading();
+ return true;
+ } catch (error) {
+ console.log(error);
+ cancelLoading();
+ spanNotifaction("¡Oh! Ocurrió un error al tratar de guardar los datos");
+ return false;
+ }
+}
+
+function showLoading() {
+ document.getElementById("save-data").classList.add("hidden");
+ document.getElementById("cancel-save").classList.add("hidden");
+ document.getElementById("loading-data-save").classList.remove("hidden");
+}
+
+function cancelLoading() {
+ document.getElementById("loading-data-save").classList.add("hidden");
+ document.getElementById("question-save").classList.remove("hidden");
+}
+
+function showOptionsSave() {
+ document.getElementById("question-save").classList.add("hidden");
+ document.getElementById("save-data").classList.remove("hidden");
+ document.getElementById("cancel-save").classList.remove("hidden");
+}
+
+function showQuestionSave() {
+ document.getElementById("question-save").classList.remove("hidden");
+ document.getElementById("save-data").classList.add("hidden");
+ document.getElementById("cancel-save").classList.add("hidden");
+}
diff --git a/tecnicas/templates/tecnicas/components/dialog-nap-puf.html b/tecnicas/templates/tecnicas/components/dialog-nap-puf.html
new file mode 100644
index 0000000000000000000000000000000000000000..2193f9c575d94331b3c3396ebebc87db000f94ed
--- /dev/null
+++ b/tecnicas/templates/tecnicas/components/dialog-nap-puf.html
@@ -0,0 +1,28 @@
+
+
+
+ Describir producto
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/components/dialog-nap-sort.html b/tecnicas/templates/tecnicas/components/dialog-nap-sort.html
new file mode 100644
index 0000000000000000000000000000000000000000..b44d008d3907574f2452164010d50a578dfdc15e
--- /dev/null
+++ b/tecnicas/templates/tecnicas/components/dialog-nap-sort.html
@@ -0,0 +1,29 @@
+
+
+
Describir Grupo:
+
Agrega palabras para describir este grupo de productos
+
+
+
+
+
Palabras agregadas:
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/components/form-scale-continue.html b/tecnicas/templates/tecnicas/components/form-scale-continue.html
index 81d4ceb823db8a5a565bb848a770d9f01317b36c..876422be1df76a9823274e613b72165516c84cd7 100644
--- a/tecnicas/templates/tecnicas/components/form-scale-continue.html
+++ b/tecnicas/templates/tecnicas/components/form-scale-continue.html
@@ -6,10 +6,11 @@
class="text-xl font-bold tracking-wide block mb-6 first-letter:uppercase">{{ word }}
{{ word.id }}
-
+
-
diff --git a/tecnicas/templates/tecnicas/components/item-list-words.html b/tecnicas/templates/tecnicas/components/item-list-words.html
new file mode 100644
index 0000000000000000000000000000000000000000..49b494aaaebad1b5f543929b37b2fe621ca290ba
--- /dev/null
+++ b/tecnicas/templates/tecnicas/components/item-list-words.html
@@ -0,0 +1,8 @@
+
+
{{ list_tester.username }}
+
+ {% for word in list_tester.words %}
+ {{ word.nombre_palabra }}
+ {% endfor %}
+
+
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/components/item_vocabulary.html b/tecnicas/templates/tecnicas/components/item_vocabulary.html
index 1d2a12ce73e3203a6fc33a767ebfe3fa68c78b73..d822f63671a342a98ed6b48c1771142dcc2daedd 100644
--- a/tecnicas/templates/tecnicas/components/item_vocabulary.html
+++ b/tecnicas/templates/tecnicas/components/item_vocabulary.html
@@ -10,9 +10,9 @@
diff --git a/tecnicas/templates/tecnicas/components/table-napping-combined.html b/tecnicas/templates/tecnicas/components/table-napping-combined.html
new file mode 100644
index 0000000000000000000000000000000000000000..62e2d614755e27193e4a7572388a764b7de79540
--- /dev/null
+++ b/tecnicas/templates/tecnicas/components/table-napping-combined.html
@@ -0,0 +1,174 @@
+{% load static %}
+{% load custom_filters %}
+
+
+
+
Información de la Sesión B
+
+
Código: {{ session_b.codigo_sesion }}
+
Técnica: {{ technique_type }}
+
Nombre: {{ session_b.nombre_sesion|default:"Sin nombre" }}
+
Catadores: {{ testers|length }}
+
+ {% if combined_data.vocabulary_info %}
+
+ Vocabulario: {{ combined_data.vocabulary_info.nombre }}
+
+ {% endif %}
+
+ {% if combined_data.scale_info %}
+
Escala: {{ combined_data.scale_info.type }}
+
Longitud: {{ combined_data.scale_info.size }}
+ {% endif %}
+
+ {% if combined_data.num_repetitions %}
+
Repeticiones: {{ combined_data.num_repetitions }}
+ {% endif %}
+
+
+ Palabras utilizadas:
+ {{ combined_data.all_words|join:", " }}
+
+
+
+
+
+
+
+
+
+ Producto
+ {% for tester in testers %}
+
+ X{{ forloop.counter }}
+
+
+ Y{{ forloop.counter }}
+
+ {% endfor %}
+ {% for word in combined_data.all_words %}
+
+ {{ word }}
+
+ {% endfor %}
+
+
+
+ {% for product, coordinates_tester in coordinates_no_mode.items %}
+
+ {{ product }}
+ {% for tester in testers %}
+ {% with points=coordinates_tester|get_item:tester.user.username %}
+
+ {{ points.px }}
+
+
+ {{ points.py }}
+
+ {% endwith %}
+ {% endfor %}
+ {% for word in combined_data.all_words %}
+
+ {% if combined_data.word_frequencies %}
+ {% with freq=combined_data.word_frequencies|get_item:product|get_item:word %}
+ {% if freq %}{{ freq }}{% else %}0{% endif %}
+ {% endwith %}
+ {% elif combined_data.word_averages %}
+ {% with avg=combined_data.word_averages|get_item:product|get_item:word %}
+ {% if avg %}{{ avg|floatformat:2 }}{% else %}0.00{% endif %}
+ {% endwith %}
+ {% else %}
+ 0
+ {% endif %}
+
+ {% endfor %}
+
+ {% endfor %}
+
+
+
+
+
+
+ Descargar datos combinados en CSV
+
+
+
+
+
diff --git a/tecnicas/templates/tecnicas/components/table-napping-no-mode.html b/tecnicas/templates/tecnicas/components/table-napping-no-mode.html
new file mode 100644
index 0000000000000000000000000000000000000000..956929c5b6cc5893bfaff8c37baba8f9a89f78f0
--- /dev/null
+++ b/tecnicas/templates/tecnicas/components/table-napping-no-mode.html
@@ -0,0 +1,51 @@
+{% load custom_filters %}
+
+
+ Datos de Napping sin modalidad
+
+
+
+
+
+
+ Producto
+ {% for tester in testers %}
+
+ X{{ forloop.counter }}
+
+
+ Y{{ forloop.counter }}
+
+ {% endfor %}
+
+
+
+ {% for product, coordinates_tester in coordinates_no_mode.items %}
+
+ {{ product }}
+ {% for tester in testers %}
+ {% with points=coordinates_tester|get_item:tester.user.username %}
+
+ {{ points.px }}
+
+
+ {{ points.py }}
+
+ {% endwith %}
+ {% endfor %}
+
+ {% endfor %}
+
+
+
+
+
+
+ Descargar datos en CSV
+
+
+
+
+{% load static %}
+
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/components/table-napping-puf.html b/tecnicas/templates/tecnicas/components/table-napping-puf.html
new file mode 100644
index 0000000000000000000000000000000000000000..53b242dcd4ee59f673e543c55231245661ae906f
--- /dev/null
+++ b/tecnicas/templates/tecnicas/components/table-napping-puf.html
@@ -0,0 +1,63 @@
+{% load custom_filters %}
+
+
+ Datos de Napping con Perfil Ultra Flash
+
+
+
+
+
+
+ Producto
+ {% for tester in testers %}
+
+ X{{ forloop.counter }}
+
+
+ Y{{ forloop.counter }}
+
+ {% endfor %}
+ {% for word in all_words %}
+
+ {{ word }}
+
+ {% endfor %}
+
+
+
+ {% for product, coordinates_tester in coordinates_no_mode.items %}
+
+ {{ product }}
+ {% for tester in testers %}
+ {% with points=coordinates_tester|get_item:tester.user.username %}
+
+ {{ points.px }}
+
+
+ {{ points.py }}
+
+ {% endwith %}
+ {% endfor %}
+ {% for word in all_words %}
+ {% with word_freq=word_frequencies|get_item:product|get_item:word %}
+
+ {% if word_freq %}{{ word_freq }}{% else %}0{% endif %}
+
+ {% endwith %}
+ {% endfor %}
+
+ {% endfor %}
+
+
+
+
+
+
+ Descargar datos en CSV
+
+
+
+
+{% load static %}
+
diff --git a/tecnicas/templates/tecnicas/components/table-napping-sorting.html b/tecnicas/templates/tecnicas/components/table-napping-sorting.html
new file mode 100644
index 0000000000000000000000000000000000000000..eb0e60b27d533d94e2e6d2e085f7a350495d235a
--- /dev/null
+++ b/tecnicas/templates/tecnicas/components/table-napping-sorting.html
@@ -0,0 +1,57 @@
+{% load custom_filters %}
+
+
+ Datos de Napping con Sorting
+
+
+
+
+
+
+ Producto
+ {% for tester in testers %}
+
+ X{{ forloop.counter }}
+
+
+ Y{{ forloop.counter }}
+
+
+ C{{ forloop.counter }}
+
+ {% endfor %}
+
+
+
+ {% for product, data_per_tester in sorting_data.items %}
+
+ {{ product }}
+ {% for tester in testers %}
+ {% with data=data_per_tester|get_item:tester.user.username %}
+
+ {{ data.px }}
+
+
+ {{ data.py }}
+
+
+ {{ data.words }}
+
+ {% endwith %}
+ {% endfor %}
+
+ {% endfor %}
+
+
+
+
+
+
+ Descargar datos en CSV
+
+
+
+
+{% load static %}
+
diff --git a/tecnicas/templates/tecnicas/components/table-convencional.html b/tecnicas/templates/tecnicas/components/table-scales.html
similarity index 85%
rename from tecnicas/templates/tecnicas/components/table-convencional.html
rename to tecnicas/templates/tecnicas/components/table-scales.html
index 0f11db4798200c6e318362f83ad5c77c6332333d..8c331debc39b94332fda6935ed2d4a825b6e45e8 100644
--- a/tecnicas/templates/tecnicas/components/table-convencional.html
+++ b/tecnicas/templates/tecnicas/components/table-scales.html
@@ -1,10 +1,12 @@
{% load static %}
-
+
+ {% if sesion.tecnica.tipo_tecnica.nombre_tecnica != "rata" %}
Repetición
+ {% endif %}
Usuario
Producto
{% for palabra in palabras %}
@@ -17,7 +19,9 @@
{% for usuario, productos in catadores.items %}
{% for codigo, valores in productos.items %}
- {{ repeticion }}
+ {% if sesion.tecnica.tipo_tecnica.nombre_tecnica != "rata" %}
+ {{ repeticion }}
+ {% endif %}
{{ usuario }}
{{ codigo }}
{% for palabra in palabras %}
diff --git a/tecnicas/templates/tecnicas/components/table-sort.html b/tecnicas/templates/tecnicas/components/table-sort.html
new file mode 100644
index 0000000000000000000000000000000000000000..9a4eb37a7d76ae20e8817c6301d2af1f53c6b666
--- /dev/null
+++ b/tecnicas/templates/tecnicas/components/table-sort.html
@@ -0,0 +1,42 @@
+{% load custom_filters %}
+{% load static %}
+
+
+
+
+
+ Producto
+ {% for tester in data_groups.testers %}
+
+ Consumidor {{ forloop.counter }}: {{ tester }}
+
+ {% endfor %}
+
+
+
+ {% for data_words in data_groups.data %}
+
+ {{ data_words.codigo_producto }}
+
+ {% for tester in data_groups.testers %}
+
+ {% for word in data_words.palabras|get_item:tester %}
+ {{word}}{% if not forloop.last %} ;{% endif %}
+ {% endfor %}
+
+ {% endfor %}
+
+ {% endfor %}
+
+
+
+
+
+
+ Descargar datos en CSV
+
+
+
+
+
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/components/table_cata.html b/tecnicas/templates/tecnicas/components/table_cata.html
new file mode 100644
index 0000000000000000000000000000000000000000..95f8ac78dad88cd38c7955ce18324bef2c131fea
--- /dev/null
+++ b/tecnicas/templates/tecnicas/components/table_cata.html
@@ -0,0 +1,50 @@
+{% load static %}
+
+
+
+
+
+ Usuario
+ Producto
+ {% for palabra in palabras %}
+ {{ palabra }}
+ {% endfor %}
+
+
+
+ {% for repeticion, catadores in calificaciones.items %}
+ {% for usuario, productos in catadores.items %}
+ {% for codigo, valores in productos.items %}
+
+ {{ usuario }}
+ {{ codigo }}
+ {% for palabra in palabras %}
+
+ {% for valor in valores %}
+ {% if valor.nombre_palabra == palabra %}
+ {% if valor.dato_valor %}
+ 1
+ {% else %}
+ 0
+ {% endif %}
+ {% endif %}
+ {% endfor %}
+
+ {% endfor %}
+
+ {% endfor %}
+ {% endfor %}
+ {% endfor %}
+
+
+
+
+
+
+ Descargar CSV
+
+
+
+
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/components/table_pf.html b/tecnicas/templates/tecnicas/components/table_pf.html
new file mode 100644
index 0000000000000000000000000000000000000000..6a4bff6e25eefdd0f0ab5447fcdce4a7138208f4
--- /dev/null
+++ b/tecnicas/templates/tecnicas/components/table_pf.html
@@ -0,0 +1,39 @@
+{% load static %}
+
+
+ Datos de usuario {{ data.tester }}
+
+
+
+
+
+ Repetición
+ Producto
+ {% for word in data.words %}
+ {{ word.nombre_palabra }}
+ {% endfor %}
+
+
+
+ {% for repeticion, data_product in data.ratings.items %}
+ {% for codigo, valores in data_product.items %}
+
+ {{ repeticion }}
+ {{ codigo }}
+ {% for word in data.words %}
+
+ {% for valor in valores %}
+ {% if valor.nombre_palabra == word.nombre_palabra %}
+ {{ valor.dato_valor }}
+ {% endif %}
+ {% endfor %}
+
+ {% endfor %}
+
+ {% endfor %}
+ {% endfor %}
+
+
+
+
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/components/table_pf_all.html b/tecnicas/templates/tecnicas/components/table_pf_all.html
new file mode 100644
index 0000000000000000000000000000000000000000..bb326d7a0a96120b444debd5104377efda3dae07
--- /dev/null
+++ b/tecnicas/templates/tecnicas/components/table_pf_all.html
@@ -0,0 +1,51 @@
+{% load custom_filters %}
+
+
+
+
+
+ Producto
+ {% for user_data in second_phase %}
+
+ {{ user_data.username }}
+
+ {% endfor %}
+
+
+ {% for user_data in second_phase %}
+ {% for word in user_data.words %}
+
+ {{ word.nombre_palabra }}
+
+ {% endfor %}
+ {% endfor %}
+
+
+
+ {% for product_code, users_ratings in data_ratings.items %}
+
+ {{ product_code }}
+
+ {% for user_data in second_phase %}
+ {% with user_ratings=users_ratings|get_item:user_data.username %}
+ {% for word in user_data.words %}
+
+ {% if user_ratings %}
+ {% for rating in user_ratings %}
+ {% if rating.palabra == word.nombre_palabra %}
+ {{ rating.valor }}
+ {% endif %}
+ {% endfor %}
+ {% else %}
+ -
+ {% endif %}
+
+ {% endfor %}
+ {% endwith %}
+ {% endfor %}
+
+ {% endfor %}
+
+
+
+
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/components/toast-container.html b/tecnicas/templates/tecnicas/components/toast-container.html
new file mode 100644
index 0000000000000000000000000000000000000000..66d06ffe54f987e33fb6218bb7dfd01759e47280
--- /dev/null
+++ b/tecnicas/templates/tecnicas/components/toast-container.html
@@ -0,0 +1,3 @@
+{% load static %}
+
+
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/create_sesion/configuracion-panel-basic.html b/tecnicas/templates/tecnicas/create_sesion/conf-panel-basic.html
similarity index 96%
rename from tecnicas/templates/tecnicas/create_sesion/configuracion-panel-basic.html
rename to tecnicas/templates/tecnicas/create_sesion/conf-panel-basic.html
index 7a0d941c37a532ac166f4363aa7946fff7dcc60c..cffbfc95bc88c5e475147a339e77c74336967b86 100644
--- a/tecnicas/templates/tecnicas/create_sesion/configuracion-panel-basic.html
+++ b/tecnicas/templates/tecnicas/create_sesion/conf-panel-basic.html
@@ -21,14 +21,14 @@
Información Basica
- Ingrese el nombre de la sesion si lo desea:
+ class="text-lg flex flex-col items-center px-2 w-full font-medium tracking-wide mb-4">
+ Nombre del proyecto:
{{ form_sesion.nombre_sesion }}
+ class="text-lg flex flex-col items-center px-2 font-medium tracking-wide flex-1">
Número de Productos:
@@ -36,14 +36,14 @@
{% if use_technique == 'escalas' %}
+ class="text-lg flex flex-col items-center px-2 font-medium tracking-wide flex-1">
Número de Catadores:
{{ form_sesion.numero_catadores }}
+ class="text-lg flex flex-col items-center px-2 font-medium tracking-wide flex-1">
Número de Repeticiones:
@@ -68,8 +68,8 @@
- Ingrese las instrucciones para la tecnica:
+ class="text-lg flex flex-col items-center px-2 w-full font-medium tracking-wide mb-4">
+ Instrucciones para la seión:
{{ form_sesion.instrucciones }}
diff --git a/tecnicas/templates/tecnicas/create_sesion/configuracion-panel-codes.html b/tecnicas/templates/tecnicas/create_sesion/conf-panel-codes.html
similarity index 100%
rename from tecnicas/templates/tecnicas/create_sesion/configuracion-panel-codes.html
rename to tecnicas/templates/tecnicas/create_sesion/conf-panel-codes.html
diff --git a/tecnicas/templates/tecnicas/create_sesion/configuracion-panel-tags.html b/tecnicas/templates/tecnicas/create_sesion/conf-panel-tags.html
similarity index 100%
rename from tecnicas/templates/tecnicas/create_sesion/configuracion-panel-tags.html
rename to tecnicas/templates/tecnicas/create_sesion/conf-panel-tags.html
diff --git a/tecnicas/templates/tecnicas/create_sesion/configuracion-panel-words.html b/tecnicas/templates/tecnicas/create_sesion/conf-panel-words.html
similarity index 100%
rename from tecnicas/templates/tecnicas/create_sesion/configuracion-panel-words.html
rename to tecnicas/templates/tecnicas/create_sesion/conf-panel-words.html
diff --git a/tecnicas/templates/tecnicas/create_sesion/creando_sesion.html b/tecnicas/templates/tecnicas/create_sesion/creating_session.html
similarity index 100%
rename from tecnicas/templates/tecnicas/create_sesion/creando_sesion.html
rename to tecnicas/templates/tecnicas/create_sesion/creating_session.html
diff --git a/tecnicas/templates/tecnicas/create_sesion/panel-basic-cata.html b/tecnicas/templates/tecnicas/create_sesion/panel-basic-cata.html
index e46054495aea374fd0d9711c98b4b3dbe13f8a33..99bb5c41f64c6848fe94599a0fcb6566ec495fc8 100644
--- a/tecnicas/templates/tecnicas/create_sesion/panel-basic-cata.html
+++ b/tecnicas/templates/tecnicas/create_sesion/panel-basic-cata.html
@@ -22,15 +22,15 @@
- Ingrese el nombre de la sesion si lo desea:
+ class="text-lg flex flex-col items-center px-2 w-full font-medium tracking-wide mb-4">
+ Nombre del proyecto:
{{ form_sesion.nombre_sesion }}
+ class="text-lg flex flex-col items-center px-2 font-medium tracking-wide flex-1">
Número de Productos:
@@ -40,8 +40,8 @@
- Ingrese las instrucciones para la tecnica:
+ class="text-lg flex flex-col items-center px-2 w-full font-medium tracking-wide mb-4">
+ Instrucciones para la seión:
{{ form_sesion.instrucciones }}
diff --git a/tecnicas/templates/tecnicas/create_sesion/panel-basic-napping.html b/tecnicas/templates/tecnicas/create_sesion/panel-basic-napping.html
new file mode 100644
index 0000000000000000000000000000000000000000..d9383bb19cbc44a5cbf66c06a1629ede59ef06cb
--- /dev/null
+++ b/tecnicas/templates/tecnicas/create_sesion/panel-basic-napping.html
@@ -0,0 +1,86 @@
+{% extends 'tecnicas/layouts/base.html' %}
+{% load static %}
+
+{% block title %}Panel Configuracion{% endblock %}
+
+{% block content %}
+
+
+ Panel de configuración
+
+ {% if error %}
+ {% include "../components/error-message.html" with message=error %}
+ {% endif %}
+ {% if message %}
+ {% include "../components/error-message.html" with message=message %}
+ {% endif %}
+
+
+
+
+{% endblock %}
+
+{% block extra_js %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/create_sesion/panel-basic-pf.html b/tecnicas/templates/tecnicas/create_sesion/panel-basic-pf.html
new file mode 100644
index 0000000000000000000000000000000000000000..46784d84b1fd58a198e47e9a59efb3c7da0aa86b
--- /dev/null
+++ b/tecnicas/templates/tecnicas/create_sesion/panel-basic-pf.html
@@ -0,0 +1,78 @@
+{% extends 'tecnicas/layouts/base.html' %}
+{% load static %}
+
+{% block title %}Panel Configuracion{% endblock %}
+
+{% block content %}
+
+
+ Panel de configuración
+
+ {% if error %}
+ {% include "../components/error-message.html" with message=error %}
+ {% endif %}
+ {% if message %}
+ {% include "../components/error-message.html" with message=message %}
+ {% endif %}
+
+
+
+
+{% endblock %}
+
+{% block extra_js %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/create_sesion/panel-basic-sort.html b/tecnicas/templates/tecnicas/create_sesion/panel-basic-sort.html
new file mode 100644
index 0000000000000000000000000000000000000000..dfcc30a68152cac87120fca90b5283ad483b7a36
--- /dev/null
+++ b/tecnicas/templates/tecnicas/create_sesion/panel-basic-sort.html
@@ -0,0 +1,71 @@
+{% extends 'tecnicas/layouts/base.html' %}
+{% load static %}
+
+{% block title %}Panel Configuracion{% endblock %}
+
+{% block content %}
+
+
+ Panel de configuración
+
+ {% if error %}
+ {% include "../components/error-message.html" with message=error %}
+ {% endif %}
+ {% if message %}
+ {% include "../components/error-message.html" with message=message %}
+ {% endif %}
+
+
+
+
+{% endblock %}
+
+{% block extra_js %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/create_sesion/seleccion-tecnica.html b/tecnicas/templates/tecnicas/create_sesion/select-tecnica.html
similarity index 100%
rename from tecnicas/templates/tecnicas/create_sesion/seleccion-tecnica.html
rename to tecnicas/templates/tecnicas/create_sesion/select-tecnica.html
diff --git a/tecnicas/templates/tecnicas/forms_tester/init_pf_test.html b/tecnicas/templates/tecnicas/forms_tester/init_pf_test.html
new file mode 100644
index 0000000000000000000000000000000000000000..d813c0155b9ccded38fcc68e4e843bf8dda05dd7
--- /dev/null
+++ b/tecnicas/templates/tecnicas/forms_tester/init_pf_test.html
@@ -0,0 +1,123 @@
+{% extends 'tecnicas/layouts/base.html' %}
+{% load static %}
+
+{% block title %}Detalles Sesion{% endblock %}
+
+{% block content %}
+
+
+
+
+ Panel principal de Catadores
+
+
+
+
+
+ Información sobre la sesión en la que participa
+
+
+
+ {% if error %}
+ {% include "../components/error-message.html" with message=error %}
+ {% endif %}
+ {% if message %}
+ {% include "../components/error-message.html" with message=message %}
+ {% endif %}
+
+
+
+
+
+ Código:
+
+
+ {{ session.codigo_sesion }}
+
+
+
+
+
+ {% if session.nombre_sesion %}
+ {{ session.nombre_sesion }}
+ {% else %}
+ Sin nombre
+ {% endif %}
+
+
+
+
+
+ Esta sesión usa la técnica {{ session.tecnica.tipo_tecnica }}
+
+
+
+
+
+ Actividad: {{ activity }}
+
+
+
+ {% if repetition >= 1 %}
+
+
+ Repetición de la calificación: {{ repetition }}
+
+
+ {% endif %}
+
+
+
+ {{ session.tecnica.instrucciones }}
+
+
+
+
+
+
+
+ {% if has_ended %}
+ {% if repetition >= 1 %}
+
+
Finalizaste la repetición
+
+ {% else %}
+
+
Finalizaste la actividad
+
+ {% endif %}
+ {% else %}
+
+ Entrar en fase o repetición
+
+
+
+
+ {% endif %}
+
+
+ Regresar a la lista
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
+
+{% block extra_js %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/forms_tester/init_session.html b/tecnicas/templates/tecnicas/forms_tester/init_scales_test.html
similarity index 100%
rename from tecnicas/templates/tecnicas/forms_tester/init_session.html
rename to tecnicas/templates/tecnicas/forms_tester/init_scales_test.html
diff --git a/tecnicas/templates/tecnicas/forms_tester/init_test_napping.html b/tecnicas/templates/tecnicas/forms_tester/init_test_napping.html
new file mode 100644
index 0000000000000000000000000000000000000000..8bc0543007cf184cc9bcfeccd7d3d06f551f6f5c
--- /dev/null
+++ b/tecnicas/templates/tecnicas/forms_tester/init_test_napping.html
@@ -0,0 +1,113 @@
+{% extends 'tecnicas/layouts/base.html' %}
+{% load static %}
+
+{% block title %}Detalles Sesion{% endblock %}
+
+{% block content %}
+
+
+
+
+ Panel principal de Catadores
+
+
+
+
+
+ Información sobre la sesión en la que participa
+
+
+
+ {% if error %}
+ {% include "../components/error-message.html" with message=error %}
+ {% endif %}
+ {% if message %}
+ {% include "../components/error-message.html" with message=message %}
+ {% endif %}
+
+
+
+
+
+ Código:
+
+
+ {{ session.codigo_sesion }}
+
+
+
+
+
+ {% if session.nombre_sesion %}
+ {{ session.nombre_sesion }}
+ {% else %}
+ Sin nombre
+ {% endif %}
+
+
+
+
+
+ Esta sesión usa la técnica {{ session.tecnica.tipo_tecnica }}
+
+
+
+
+
+
+
+ {{ session.tecnica.instrucciones }}
+
+
+
+
+
+
+
+
+
+ {% if has_ended %}
+
+
Finalizaste la sesión
+
+ {% else %}
+
+ Iniciar técnica
+
+
+
+
+ {% endif %}
+
+
+ Regresar a la lista
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
+
+{% block extra_js %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/forms_tester/init_test_sort.html b/tecnicas/templates/tecnicas/forms_tester/init_test_sort.html
new file mode 100644
index 0000000000000000000000000000000000000000..5c53e2f765783dd45de9cf3bbcbbce7c709e44e0
--- /dev/null
+++ b/tecnicas/templates/tecnicas/forms_tester/init_test_sort.html
@@ -0,0 +1,108 @@
+{% extends 'tecnicas/layouts/base.html' %}
+{% load static %}
+
+{% block title %}Detalles Sesion{% endblock %}
+
+{% block content %}
+
+
+
+
+ Panel principal de Catadores
+
+
+
+
+
+ Información sobre la sesión en la que participa
+
+
+
+ {% if error %}
+ {% include "../components/error-message.html" with message=error %}
+ {% endif %}
+ {% if message %}
+ {% include "../components/error-message.html" with message=message %}
+ {% endif %}
+
+
+
+
+
+ Código:
+
+
+ {{ session.codigo_sesion }}
+
+
+
+
+
+ {% if session.nombre_sesion %}
+ {{ session.nombre_sesion }}
+ {% else %}
+ Sin nombre
+ {% endif %}
+
+
+
+
+
+ Esta sesión usa la técnica {{ session.tecnica.tipo_tecnica }}
+
+
+
+
+
+
+
+ {{ session.tecnica.instrucciones }}
+
+
+
+
+
+
+
+ {% if has_ended %}
+
+
Finalizaste la sesión
+
+ {% else %}
+
+ Iniciar técnica
+
+
+
+
+ {% endif %}
+
+
+ Regresar a la lista
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
+
+{% block extra_js %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/forms_tester/test_cata.html b/tecnicas/templates/tecnicas/forms_tester/test_cata.html
new file mode 100644
index 0000000000000000000000000000000000000000..3af5551c23acc4a079fdbfda56e2392afc0f1970
--- /dev/null
+++ b/tecnicas/templates/tecnicas/forms_tester/test_cata.html
@@ -0,0 +1,119 @@
+{% extends 'tecnicas/layouts/base.html' %}
+
+{% load static %}
+
+{% block title %}Convencional{% endblock %}
+
+{% block content %}
+
+
+
+
+ Sesión usando técnica
+ {{ session.tecnica.tipo_tecnica }}
+
+
+ Salir de la sesión
+
+
+
+
+
+
+
+
+
+ {% if error %}
+ {% include "../components/error-message.html" with message=error %}
+ {% endif %}
+ {% if message %}
+ {% include "../components/error-message.html" with message=message %}
+ {% endif %}
+
+
+
+
+ {{ session.tecnica.instrucciones }}
+
+
+
+
+
+ Producto:
+
+
+ {{ product }}
+ {{ product.id }}
+
+
+
+
+ Repetición:
+
+
+ {{ session.tecnica.repeticion }}
+
+
+
+
+
+
+ Soy el mensaje para responder
+
+
+
+
+
+
+
+
+
+
Confirma tu elección
+
+
+
+
+ Cancelar
+
+
+ Confirmar
+
+
+
+
+
+
+
+
+{% endblock %}
+
+{% block extra_js %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/forms_tester/convencional.html b/tecnicas/templates/tecnicas/forms_tester/test_convencional.html
similarity index 98%
rename from tecnicas/templates/tecnicas/forms_tester/convencional.html
rename to tecnicas/templates/tecnicas/forms_tester/test_convencional.html
index 1184b2f7fc7f18c0da904fa2c2f05132d9f51f6a..24faa948f6bdc207e9e89653ae6c000b1d14d389 100644
--- a/tecnicas/templates/tecnicas/forms_tester/convencional.html
+++ b/tecnicas/templates/tecnicas/forms_tester/test_convencional.html
@@ -104,7 +104,7 @@
{% with path_con="../components/form-scale-continue.html" path_str="../components/form-scale-structure.html" %}
{% if type_scale == "continua" %}
{% for word in words %}
- {% include path_con with word=word tags=tags scale=scale id_tecnica=session.tecnica.id %}
+ {% include path_con with word=word tags=tags scale=scale size_scale=size_scale %}
{% endfor %}
{% elif type_scale == "estructurada" %}
{% for word in words %}
diff --git a/tecnicas/templates/tecnicas/forms_tester/test_napping.html b/tecnicas/templates/tecnicas/forms_tester/test_napping.html
new file mode 100644
index 0000000000000000000000000000000000000000..0faa331297617d90ea4f40b15ed1b232501c720c
--- /dev/null
+++ b/tecnicas/templates/tecnicas/forms_tester/test_napping.html
@@ -0,0 +1,155 @@
+{% extends 'tecnicas/layouts/base.html' %}
+
+{% load static %}
+
+{% block title %}Napping{% endblock %}
+
+{% block content %}
+
+
+
+
+ Sesión usando técnica
+ {{ session.tecnica.tipo_tecnica }}
+
+
+ Salir de la sesión
+
+
+
+
+
+
+
+
+
+ {% if error %}
+ {% include "../components/error-message.html" with message=error %}
+ {% endif %}
+
+
+
+
+ {{ session.tecnica.instrucciones }}
+
+
+
+
+
+ Modalidad: {% if mode != "sin modalidad" %}{{ mode }} {% else %} Napping {% endif %}
+
+
+
+
+
+ Productos en la sesión
+
+ {% for product in products %}
+
+ {{ product.codigoProducto }}
+
+ {% endfor %}
+
+
+
+
+
+
+
60cm (X)
+
40cm (Y)
+
0
+
+ {% for point in data_points %}
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+ Para guardar los datos sin finalizar su sesión use el botón
+ "Guardar progreso"
+
+
+
+
+
+
+
+
+
+ Si usas el botón “Salir de la sesión” asegúrese de guardar el progreso de lo contrario
+ perderás todo lo que no hayas guardado antes
+
+
+ Con el botón “Finalizar” , se guardan las posiciones, sales de la sesión y no
+ podrás ingresar otra vez
+
+
+
+
+
+
+ Guardar progreso
+
+ {% if mode == "perfil ultra flash" %}
+
+ Continuar a descripción
+
+ {% endif %}
+
+
+ ¿Finalizar sesión?
+
+
+ Finalizar
+
+
+ Cancelar
+
+
+
+
+
+ {% include "../components/toast-container.html" %}
+
+
+{% endblock %}
+
+{% block extra_js %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/forms_tester/test_napping_puf.html b/tecnicas/templates/tecnicas/forms_tester/test_napping_puf.html
new file mode 100644
index 0000000000000000000000000000000000000000..b662bf40df0c4bb878bdc2cf2ba93f79469a2b16
--- /dev/null
+++ b/tecnicas/templates/tecnicas/forms_tester/test_napping_puf.html
@@ -0,0 +1,172 @@
+{% extends 'tecnicas/layouts/base.html' %}
+{% load static %}
+{% load custom_filters %}
+
+{% block title %}Napping{% endblock %}
+
+{% block content %}
+
+
+
+
+ Sesión usando técnica
+ {{ session.tecnica.tipo_tecnica }}
+
+
+ Salir de la sesión
+
+
+
+
+
+
+
+
+
+ {% if error %}
+ {% include "../components/error-message.html" with message=error %}
+ {% endif %}
+
+
+
+
+ Modalidad: {% if mode != "sin modalidad" %}{{ mode }} {% else %} Nappging {% endif %}
+
+
+
+
+
+
+ Productos en la sesión
+
+
Instrucciones
+
+ {{ session.tecnica.instrucciones }}
+
+
+
+
+ {% for product in products %}
+
+ {{ product.codigoProducto }}
+
+ {% endfor %}
+
+
+
+
+
+
+
60cm (X)
+
40cm (Y)
+
0
+
+ {% for point in data_points %}
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+ Para guardar los datos sin finalizar su sesión use el botón
+ "Guardar progreso"
+
+
+
+
+
+
+
+
+
+ Si ya has agregado atributos a un punto y guardas el progreso ya no será posible
+ volver a mover los puntos
+
+
+
+
+
+
+
+
+
+
+ Si usas el botón “Salir de la sesión” asegúrese de guardar el progreso de lo contrario
+ perderás todo lo que no hayas guardado antes
+
+
+ Con el botón “Finalizar” , se guardan las posiciones, sales de la sesión y no
+ podrás ingresar otra vez
+
+
+
+
+
+
+ Guardar progreso
+
+
+ Continuar a descripción
+
+
+
+ ¿Finalizar sesión?
+
+
+ Finalizar
+
+
+ Cancelar
+
+
+
+
+
+
+ {% include "../components/dialog-nap-puf.html" %}
+
+ {% include "../components/toast-container.html" %}
+
+
+{% endblock %}
+
+{% block extra_js %}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/forms_tester/test_napping_sort.html b/tecnicas/templates/tecnicas/forms_tester/test_napping_sort.html
new file mode 100644
index 0000000000000000000000000000000000000000..d8b60ae9abbc9857131fb45ec04565423deb7451
--- /dev/null
+++ b/tecnicas/templates/tecnicas/forms_tester/test_napping_sort.html
@@ -0,0 +1,198 @@
+{% extends 'tecnicas/layouts/base.html' %}
+
+{% load static %}
+
+{% block title %}Napping{% endblock %}
+
+{% block content %}
+
+
+
+
+ Sesión usando técnica
+ {{ session.tecnica.tipo_tecnica }}
+
+
+ Salir de la sesión
+
+
+
+
+
+
+
+
+
+ {% if error %}
+ {% include "../components/error-message.html" with message=error %}
+ {% endif %}
+
+
+
+
+ Modalidad: {% if mode != "sin modalidad" %}{{ mode }} {% else %} Napping {% endif %}
+
+
+
+
+
+
+ Productos en la sesión
+
+
Instrucciones
+
+ {{ session.tecnica.instrucciones }}
+
+
+
+
+
+
+ {% for product in products %}
+
+ {{ product.codigoProducto }}
+
+ {% endfor %}
+
+
+
+
+ Continuar a Agrupación
+
+
+ Continuar a Descripción
+
+
+
+
+
+
+
+
+ Crear Grupo
+
+
+ Disolver Grupo
+
+
+
+ {% if groups %}
+
+ {% for group in groups %}
+
+ {% for product in group.products %}
+
+ {% endfor %}
+
+ {% endfor %}
+
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
60cm (X)
+
40cm (Y)
+
0
+
+ {% for point in data_points %}
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+ Para guardar los datos sin finalizar su sesión use el botón
+ "Guardar progreso"
+
+
+
+
+
+
+
+
+
+ Si usas el botón "Salir de la sesión" asegúrese de guardar el progreso de lo contrario
+ perderás todo lo que no hayas guardado antes
+
+
+ Con el botón "Finalizar" , se guardan las posiciones, sales de la sesión y no
+ podrás ingresar otra vez
+
+
+
+
+
+
+ Guardar progreso
+
+
+
+ ¿Finalizar sesión?
+
+
+ Finalizar
+
+
+ Cancelar
+
+
+
+
+
+
+ {% include "../components/dialog-nap-sort.html" with form=form %}
+
+ {% include "../components/toast-container.html" %}
+
+
+{% endblock %}
+
+{% block extra_js %}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/forms_tester/test_pf_list_words.html b/tecnicas/templates/tecnicas/forms_tester/test_pf_list_words.html
new file mode 100644
index 0000000000000000000000000000000000000000..eec8ec402708a7f98c0d55b26487c533f85a48da
--- /dev/null
+++ b/tecnicas/templates/tecnicas/forms_tester/test_pf_list_words.html
@@ -0,0 +1,181 @@
+{% extends 'tecnicas/layouts/base.html' %}
+
+{% load static %}
+
+{% block title %}Convencional{% endblock %}
+
+{% block content %}
+
+
+
+
+ Sesión usando técnica
+ {{ session.tecnica.tipo_tecnica }}
+
+
+ Salir de la sesión
+
+
+
+
+
+
+
+
+
+ {% if error %}
+ {% include "../components/error-message.html" with message=error %}
+ {% endif %}
+
+ {% if not initial_phase %}
+
+
+
+
+
+ Para poder compartir tu nueva lista de palabras con los demás Catadores, debes usar el botón de
+ “Guardar y comprobar palabras actuales”
+
+
+ {% endif %}
+
+
+
+
+ {{ session.tecnica.instrucciones }}
+
+
+
+ {% if initial_phase %}
+
+ Fase 1 :
+
+
+ Lista inicial
+
+ {% else %}
+
+ Fase 2 :
+
+
+ Lista Final
+
+ {% endif %}
+
+
+
+
+
+
+
+
+ Características sensoriales
+
+
+
+
+
+
+
+
+
+
+ Guardar y comprobar palabras actuales
+
+
+
+ Finalizar la sesión
+
+
+
+
+
+
+
+
+
+ Si usas el botón “Salir de la sesión” asegúrese de guardar las palabras de lo contrario las
+ palabras no se guardaran pero podrás ingresar otra vez
+
+
+ Con el botón “Finalizar la sesión” , se guardan las palabras, sales de la sesión y no podrás
+ ingresar otra vez
+
+
+
+
+ {% if not initial_phase %}
+
+
+
+ Listas de los demás participantes
+
+
+
+
+
+ Actualizar las listas
+
+
+
+
+ {% endif %}
+
+
+{% endblock %}
+
+{% block extra_js %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/forms_tester/test_pf_rating_list.html b/tecnicas/templates/tecnicas/forms_tester/test_pf_rating_list.html
new file mode 100644
index 0000000000000000000000000000000000000000..09f140169333fb3919837fa112b6bf8abe64afa2
--- /dev/null
+++ b/tecnicas/templates/tecnicas/forms_tester/test_pf_rating_list.html
@@ -0,0 +1,131 @@
+{% extends 'tecnicas/layouts/base.html' %}
+
+{% load static %}
+
+{% block title %}Convencional{% endblock %}
+
+{% block content %}
+
+
+
+
+ Sesión usando técnica
+ {{ session.tecnica.tipo_tecnica }}
+
+
+ Salir de la sesión
+
+
+
+
+
+
+
+
+
+ {% if error %}
+ {% include "../components/error-message.html" with message=error %}
+ {% endif %}
+
+
+
+
+ {{ session.tecnica.instrucciones }}
+
+
+
+
+
+ Fase 3 :
+
+
+ Evaluación de Atributos
+
+
+
+
+
+
+ Repetición:
+
+
+ {{ repetition }}
+
+
+
+
+
+
+
+ El atributo que se está evaluando:
+
+
+ "{{ word }}"
+
+
+
+
+
+
+
+ Productos en la sesión
+
+ {% for product in products %}
+
{{ product.codigoProducto }}
+ {% endfor %}
+
+
+
+
+ Escala ordinaria
+
+ {% for product in products %}
+
+
+ {{ forloop.counter }}
+
+ {% endfor %}
+
+
+
+
+
+ ➖
+
+ ➕
+
+ Valores de menor a mayor
+
+
+
+
+
+ ¿Guardar datos actuales?
+
+
+ Guardar
+
+
+ Cancelar
+
+
+
+
+
+ {% include "../components/toast-container.html" %}
+
+
+{% endblock %}
+
+{% block extra_js %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/forms_tester/test_sort.html b/tecnicas/templates/tecnicas/forms_tester/test_sort.html
new file mode 100644
index 0000000000000000000000000000000000000000..88f499c966b25a24e7602b10899a299534e1b48a
--- /dev/null
+++ b/tecnicas/templates/tecnicas/forms_tester/test_sort.html
@@ -0,0 +1,258 @@
+{% extends 'tecnicas/layouts/base.html' %}
+
+{% load static %}
+
+{% block title %}Convencional{% endblock %}
+
+{% block content %}
+
+
+
+
+ Sesión usando técnica
+ {{ session.tecnica.tipo_tecnica }}
+
+
+ Salir de la sesión
+
+
+
+
+
+
+
+
+
+ {% if error %}
+ {% include "../components/error-message.html" with message=error %}
+ {% endif %}
+
+
+
+
+ {{ session.tecnica.instrucciones }}
+
+
+
+
+
+ Agrupación de productos
+
+
+
+
+
+
+
+
+
+
+ Productos en la sesión
+
+ {% for product in products %}
+
+ {{ product.codigoProducto }}
+
+ {% endfor %}
+
+
+
+
+
+ Puedes crear los grupos aquí
+
+
+
+
+ Arrastra aquí para borrar
+
+
+
+
+
+
+
+
+ ➕ Grupo
+
+
+
+
+
+
+
+ {% if grups_products %}
+ {% for group in grups_products %}
+
+
+ {% for product in group.productos.all %}
+
+ {{ product.codigoProducto }}
+
+ {% endfor %}
+
+
+
+
+
+ {% for word in group.palabras.all %}
+
+ {{ word.nombre_palabra }}
+
+ ➖
+
+
+ {% endfor %}
+
+
+
+
+
+ ¿Remover este grupo?
+
+
+ Remover
+
+
+ Conservar
+
+
+
+ {% endfor %}
+
+ {% else %}
+
+
+
+ Agrupación de productos
+
+
+
+
+
+
+
+ Lista de atributos
+
+
+
+
+
+ ¿Remover este grupo?
+
+
+ Remover
+
+
+ Conservar
+
+
+
+ {% endif %}
+
+
+
+
+
+
+
+
+
+ Para guardar los datos sin finalizar su sesión use el botón
+ "Guardar progreso"
+
+
+
+
+
+
+
+
+
+ Si usas el botón “Salir de la sesión” asegúrese de guardar el progreso de lo contrario
+ perderás todo lo que no hayas guardado antes
+
+
+ Con el botón “Finalizar la sesión” , se guardan las palabras, sales de la sesión y no
+ podrás ingresar otra vez
+
+
+
+
+
+
+ Guardar progreso
+
+
+
+ ¿Finalizar sesión?
+
+
+ Finalizar
+
+
+ Cancelar
+
+
+
+
+
+
+ {% include "../components/toast-container.html" %}
+
+
+{% endblock %}
+
+{% block extra_js %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/manage_sesions/details-session-cata.html b/tecnicas/templates/tecnicas/manage_sesions/details-session-cata.html
new file mode 100644
index 0000000000000000000000000000000000000000..dc9e8a86af1b381640b1ca4e39882d983c4273b9
--- /dev/null
+++ b/tecnicas/templates/tecnicas/manage_sesions/details-session-cata.html
@@ -0,0 +1,238 @@
+{% extends 'tecnicas/layouts/base.html' %}
+{% load static %}
+
+{% block title %}Detalles Sesion{% endblock %}
+
+{% block content %}
+
+
+
+
+ Detalles de la sesión
+
+
+
+ Volver a las Sesiones
+
+
+
+
+ {% if message %}
+ {% include "../components/error-message.html" with message=message %}
+ {% endif %}
+ {% if error %}
+ {% include "../components/error-message.html" with message=error %}
+ {% endif %}
+
+
+ Información general
+
+
+
+
+ Código:
+
+
+ {{ sesion.codigo_sesion }}
+
+
+
+
+
+ Nombre:
+
+
+ {% if sesion.nombre_sesion %}
+ {{ sesion.nombre_sesion }}
+ {% else %}
+ Sin nombre
+ {% endif %}
+
+
+
+
+
+ Fecha creación:
+
+
+ {{ sesion.fechaCreacion }}
+
+
+
+
+
+ Estado:
+
+
+ {% if sesion.activo %}
+ En proceso
+ {% else %}
+ Listo para iniciar repetición
+ {% endif %}
+
+
+
+
+
+ Estilo palabras:
+
+
+ {{ sesion.tecnica.id_estilo }}
+
+
+
+
+
+ Técnica:
+
+
+ {{ sesion.tecnica.tipo_tecnica }}
+
+
+
+
+
+ Pueden ingresar tantos catadores como se desee
+
+
+
+
+
+ La técnica usada solo puede realizarse una vez, hay una única repetición
+
+
+
+
+
+ Instrucciones:
+
+
+ {{ sesion.tecnica.instrucciones }}
+
+
+
+
+
+ Palabras usadas para calificar
+
+
+ {% for palabra in palabras %}
+
+ {% endfor %}
+
+
+
+
+ Acciones disponibles
+
+
+ {% if not sesion.activo %}
+ {% if fin_repeticiones %}
+
+
+ Sesión finalizada
+
+
+ {% else %}
+
+ Iniciar repetición
+
+
+
+
+ {% endif %}
+ {% else %}
+
+
+ Monitorear repetición
+
+
+
+
+
+ {% endif %}
+
+ borrar
+
+
+
+
+
+
+
+
+
+ ¡¡¡Cuidado!!!
+
+
+ Esta acción no es reversible, si la continua también borrara todos los datos recopilados
+ relacionados a esta sesión y no podrá volver a recuperarlos
+
+
+
+ ¿Estás seguro de continuar?
+
+
+
+ Estoy seguro
+
+
+ No, cancelar
+
+
+
+
+
+
+
+ Datos obtenidos
+
+
+
+
+
+ Los valores para este tipo de técnica son booleanos, pueden ser 1 o pueden ser 0
+
+
+ {% if existen_calificaciones %}
+ {% include "../components/table_cata.html" with calificaciones=calificaciones palabras=palabras sesion=sesion %}
+ {% else %}
+ {% include "../components/error-message.html" with message='Sin calificaciones que mostrar aún' %}
+ {% endif %}
+
+
+
+
+
+{% endblock %}
+
+{% block extra_js %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/manage_sesions/details-session-napping.html b/tecnicas/templates/tecnicas/manage_sesions/details-session-napping.html
new file mode 100644
index 0000000000000000000000000000000000000000..6707b4088ab06c567ab920e51c062450d61dfe3f
--- /dev/null
+++ b/tecnicas/templates/tecnicas/manage_sesions/details-session-napping.html
@@ -0,0 +1,261 @@
+{% extends 'tecnicas/layouts/base.html' %}
+{% load static %}
+
+{% block title %}Detalles Sesion{% endblock %}
+
+{% block content %}
+
+
+
+
+ Detalles de la sesión
+
+
+
+ Volver a las Sesiones
+
+
+
+
+ {% if message %}
+ {% include "../components/error-message.html" with message=message %}
+ {% endif %}
+ {% if error %}
+ {% include "../components/error-message.html" with message=error %}
+ {% endif %}
+
+
+ Información general
+
+
+
+
+ Código:
+
+
+ {{ session.codigo_sesion }}
+
+
+
+
+
+ Nombre:
+
+
+ {% if session.nombre_sesion %}
+ {{ session.nombre_sesion }}
+ {% else %}
+ Sin nombre
+ {% endif %}
+
+
+
+
+
+ Fecha creación:
+
+
+ {{ session.fechaCreacion }}
+
+
+
+
+
+ Estado:
+
+
+ {{ status }}
+
+
+
+
+
+ Técnica:
+
+
+ {{ session.tecnica.tipo_tecnica }}
+
+
+
+
+
+ Modalidad: {{ mod_tech }}
+
+
+
+
+
+ Catadores Max. {{ session.tecnica.limite_catadores }}
+
+
+
+ {% if repetition %}
+
+
+ Repetición: {{ repetition }}
+
+
+ {% endif %}
+
+
+
+ Instrucciones:
+
+
+ {{ session.tecnica.instrucciones }}
+
+
+
+
+
+ Modalidades disponibles
+
+
+ {% if not session.activo %}
+ {% if finished %}
+
+
+ Sesión finalizada
+
+
+ {% else %}
+
+ Iniciar {% if mode != "sin modalidad" %}Napping con {{ mode }}{% else %}Napping{% endif %}
+
+
+
+
+ {% endif %}
+ {% else %}
+
+
+ Monitorear repetición
+
+
+
+
+
+ {% endif %}
+
+ borrar
+
+
+
+
+
+
+
+
+
+ ¡¡¡Cuidado!!!
+
+
+ Esta acción no es reversible, si la continua también borrara todos los datos recopilados
+ relacionados a esta sesión y no podrá volver a recuperarlos
+
+
+
+ ¿Estás seguro de continuar?
+
+
+
+ Estoy seguro
+
+
+ No, cancelar
+
+
+
+
+
+
+
+ Datos obtenidos
+
+
+ {% if there_data %}
+ {% if mode == "perfil ultra flash" %}
+ {% include "../components/table-napping-puf.html" with testers=testers coordinates_no_mode=coordinates_no_mode word_frequencies=word_frequencies all_words=all_words %}
+ {% elif mode == "sorting" %}
+ {% include "../components/table-napping-sorting.html" with testers=testers sorting_data=sorting_data %}
+ {% else %}
+ {% include "../components/table-napping-no-mode.html" with testers=testers coordinates_no_mode=coordinates_no_mode %}
+ {% endif %}
+ {% else %}
+ {% include "../components/error-message.html" with message='Sin datos por mostrar aún' %}
+ {% endif %}
+
+ {% if mode == "sin modalidad" and finished %}
+
+ Combinar datos con otra sesión
+
+
+
+ Puede combinar los datos de esta sesión de Napping con otra sesión que use CATA, RATA o Escalas.
+ Ambas sesiones deben tener los mismos productos y catadores
+
+
+
+
+ {% endif %}
+
+ {% if combined_data %}
+
+ Datos Combinados (Napping + {{ session_b_technique_type }})
+
+ {% include "../components/table-napping-combined.html" with session=session session_b=session_b technique_type=session_b_technique_type combined_data=combined_data testers=testers coordinates_no_mode=coordinates_no_mode %}
+ {% endif %}
+
+
+
+
+
+{% endblock %}
+
+{% block extra_js %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/manage_sesions/details-session-pf.html b/tecnicas/templates/tecnicas/manage_sesions/details-session-pf.html
new file mode 100644
index 0000000000000000000000000000000000000000..d25a3361f36b7186de37a6ff52e35202b14f5f67
--- /dev/null
+++ b/tecnicas/templates/tecnicas/manage_sesions/details-session-pf.html
@@ -0,0 +1,289 @@
+{% extends 'tecnicas/layouts/base.html' %}
+{% load static %}
+
+{% block title %}Detalles Sesion{% endblock %}
+
+{% block content %}
+
+
+
+
+ Detalles de la sesión
+
+
+
+ Volver a las Sesiones
+
+
+
+
+ {% if message %}
+ {% include "../components/error-message.html" with message=message %}
+ {% endif %}
+ {% if error %}
+ {% include "../components/error-message.html" with message=error %}
+ {% endif %}
+
+
+ Información general
+
+
+
+
+ Código:
+
+
+ {{ sesion.codigo_sesion }}
+
+
+
+
+
+ Nombre:
+
+
+ {% if sesion.nombre_sesion %}
+ {{ sesion.nombre_sesion }}
+ {% else %}
+ Sin nombre
+ {% endif %}
+
+
+
+
+
+ Fecha creación:
+
+
+ {{ sesion.fechaCreacion }}
+
+
+
+
+
+ Estado:
+
+
+ {{ estado }}
+
+
+
+
+
+ Técnica:
+
+
+ {{ sesion.tecnica.tipo_tecnica }}
+
+
+
+
+
+ Catadores Max. {{ use_technique.limite_catadores }}
+
+
+
+
+
+ Repetición Actual {{ repeticion }}
+
+
+
+
+
+ Repeticiones Max. {{ repeticiones_max }}
+
+
+
+
+
+ Instrucciones:
+
+
+ {{ sesion.tecnica.instrucciones }}
+
+
+
+
+
+ Acciones disponibles
+
+
+ {% if not sesion.activo %}
+ {% if finished %}
+
+
+ Sesión finalizada
+
+
+ {% else %}
+
+ Iniciar fase o repetición
+
+
+
+
+ {% endif %}
+ {% else %}
+
+
+ Monitorear repetición
+
+
+
+
+
+ {% endif %}
+
+ borrar
+
+
+
+
+
+
+
+
+
+ ¡¡¡Cuidado!!!
+
+
+ Esta acción no es reversible, si la continua también borrara todos los datos recopilados
+ relacionados a esta sesión y no podrá volver a recuperarlos
+
+
+
+ ¿Estás seguro de continuar?
+
+
+
+ Estoy seguro
+
+
+ No, cancelar
+
+
+
+
+
+
+
+ Datos obtenidos
+
+
+
+
+
+ Prmera fase
+ Listas iniciales de Catadores
+
+
+ {% if fisrt_phase %}
+
+ {% for list_tester in fisrt_phase %}
+ {% include "../components/item-list-words.html" with list_tester=list_tester %}
+ {% endfor %}
+
+ {% else %}
+ {% include "../components/error-message.html" with message='Sin datos por mostrar aún' %}
+ {% endif %}
+
+
+
+
+
+ Segunda fase
+ Listas finales de Catadores
+
+
+ {% if second_phase %}
+
+ {% for list_tester in second_phase %}
+ {% include "../components/item-list-words.html" with list_tester=list_tester %}
+ {% endfor %}
+
+ {% else %}
+ {% include "../components/error-message.html" with message='Sin datos por mostrar aún' %}
+ {% endif %}
+
+
+
+
+ Tercera fase
+ Califiaciones de Catadores
+
+
+
+
+ Se usa una escala {{ tipo_escala }} para calificar
+
+
+ El valor máximo por calificación es {{ valor_max }}
+
+
+
+ {% if repeticion == 1 %}
+ {% if data_ratings %}
+ {% include "../components/table_pf_all.html" with second_phase=second_phase data_ratings=data_ratings %}
+ {% else %}
+ {% include "../components/error-message.html" with message='Sin datos por mostrar aún' %}
+ {% endif %}
+ {% else %}
+ {% if data_ratings %}
+
+ {% for data_tester in data_ratings %}
+ {% include "../components/table_pf.html" with data=data_tester session_name=sesion.nombre_sesion session_code=sesion.codigo_sesion %}
+ {% endfor %}
+
+
+
+ Descargar datos CSV en zip
+
+
+ Descargar datos en XLSX
+
+
+
+
+ {% else %}
+ {% include "../components/error-message.html" with message='Sin datos por mostrar aún' %}
+ {% endif %}
+ {% endif %}
+
+
+
+
+
+
+{% endblock %}
+
+{% block extra_js %}
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/manage_sesions/details-session-sort.html b/tecnicas/templates/tecnicas/manage_sesions/details-session-sort.html
new file mode 100644
index 0000000000000000000000000000000000000000..ee1beaf40b7cc3473b79154aa9d40ee9f1c5ec7e
--- /dev/null
+++ b/tecnicas/templates/tecnicas/manage_sesions/details-session-sort.html
@@ -0,0 +1,206 @@
+{% extends 'tecnicas/layouts/base.html' %}
+{% load static %}
+
+{% block title %}Detalles Sesion{% endblock %}
+
+{% block content %}
+
+
+
+
+ Detalles de la sesión
+
+
+
+ Volver a las Sesiones
+
+
+
+
+ {% if message %}
+ {% include "../components/error-message.html" with message=message %}
+ {% endif %}
+ {% if error %}
+ {% include "../components/error-message.html" with message=error %}
+ {% endif %}
+
+
+ Información general
+
+
+
+
+ Código:
+
+
+ {{ sesion.codigo_sesion }}
+
+
+
+
+
+ Nombre:
+
+
+ {% if sesion.nombre_sesion %}
+ {{ sesion.nombre_sesion }}
+ {% else %}
+ Sin nombre
+ {% endif %}
+
+
+
+
+
+ Fecha creación:
+
+
+ {{ sesion.fechaCreacion }}
+
+
+
+
+
+ Estado:
+
+
+ {{ status }}
+
+
+
+
+
+ Técnica:
+
+
+ {{ technique.tipo_tecnica }}
+
+
+
+
+
+ Catadores Max. {{ technique.limite_catadores }}
+
+
+
+
+
+ Repetición: Única
+
+
+
+
+
+
+
+ Instrucciones:
+
+
+ {{ sesion.tecnica.instrucciones }}
+
+
+
+
+
+ Acciones disponibles
+
+
+ {% if finished %}
+
+
+ Sesión finalizada
+
+
+ {% else %}
+ {% if not sesion.activo %}
+
+ Iniciar fase o repetición
+
+
+
+
+ {% else %}
+
+
+ Monitorear repetición
+
+
+
+
+
+ {% endif %}
+ {% endif %}
+
+ borrar
+
+
+
+
+
+
+
+
+
+ ¡¡¡Cuidado!!!
+
+
+ Esta acción no es reversible, si la continua también borrara todos los datos recopilados
+ relacionados a esta sesión y no podrá volver a recuperarlos
+
+
+
+ ¿Estás seguro de continuar?
+
+
+
+ Estoy seguro
+
+
+ No, cancelar
+
+
+
+
+
+
+
+ Datos obtenidos
+
+
+ {% if there_data %}
+ {% include "../components/table-sort.html" with data_groups=data_groups sesion=sesion %}
+ {% else %}
+ {% include "../components/error-message.html" with message='Sin datos por mostrar aún' %}
+ {% endif %}
+
+
+
+
+{% endblock %}
+
+{% block extra_js %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/manage_sesions/detalles-sesion.html b/tecnicas/templates/tecnicas/manage_sesions/details-session.html
similarity index 98%
rename from tecnicas/templates/tecnicas/manage_sesions/detalles-sesion.html
rename to tecnicas/templates/tecnicas/manage_sesions/details-session.html
index 3d70ae6940025eec1f6eb47dcd54b753beb6f4df..84d42fcc6f1aa7b056f600cd12ac69e3df071137 100644
--- a/tecnicas/templates/tecnicas/manage_sesions/detalles-sesion.html
+++ b/tecnicas/templates/tecnicas/manage_sesions/details-session.html
@@ -260,7 +260,7 @@
Máximo valor por dato: {{ scale.size }}
- {% include "../components/table-convencional.html" with calificaciones=calificaciones palabras=palabras sesion=sesion %}
+ {% include "../components/table-scales.html" with calificaciones=calificaciones palabras=palabras sesion=sesion %}
{% else %}
{% include "../components/error-message.html" with message='Sin calificaciones que mostrar aún' %}
diff --git a/tecnicas/templates/tecnicas/manage_sesions/monitor-session-pf.html b/tecnicas/templates/tecnicas/manage_sesions/monitor-session-pf.html
new file mode 100644
index 0000000000000000000000000000000000000000..4af4114729217f2448b64748d59ad388c28cae5f
--- /dev/null
+++ b/tecnicas/templates/tecnicas/manage_sesions/monitor-session-pf.html
@@ -0,0 +1,126 @@
+{% extends 'tecnicas/layouts/base.html' %}
+{% load static %}
+
+{% block title %}Monitoreo{% endblock %}
+
+{% block content %}
+
+
+
+ Sesión sensorial en curso
+
+
+
+
+ Código de sesión:
+ {{ code_session }}
+
+
+
+
+
+
+ {% if error %}
+ {% include "../components/error-message.html" with message=error %}
+ {% endif %}
+
+ {% if message %}
+ {% include "../components/error-message.html" with message=message %}
+ {% endif %}
+
+
+
+
+ Actualizar lista
+
+
+ Catadores activos
+
+
+
+
+
+
Participantes
+
+
+
+ Máximo:
+ {{ max_testers }}
+
+
+
+ Actuales:
+
+ {{ current_testers }}
+
+
+ Activos:
+ {{ active_testers }}
+
+
+
+
+
+
+
+
Usuario
+
Nombre
+
Estado
+
Finalizado
+
+
+
+ {% for parti in participations %}
+
+ {{ parti.catador.user.username }}
+
+ {{ parti.catador.user.first_name }}
+ {{ parti.catador.user.last_name}}
+
+
+ {% if parti.activo %}
+ Activo
+ {% else %}
+ No activo
+ {% endif %}
+
+ {% if parti.finalizado %}
+ Si
+ {% else %}
+ No
+ {% endif %}
+
+ {% endfor %}
+
+
+
+
+
+
+{% endblock %}
+
+{% block extra_js %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/manage_sesions/monitor-session-sort.html b/tecnicas/templates/tecnicas/manage_sesions/monitor-session-sort.html
new file mode 100644
index 0000000000000000000000000000000000000000..30c9234ba447f03a643deb32056ee760c1bfc6d8
--- /dev/null
+++ b/tecnicas/templates/tecnicas/manage_sesions/monitor-session-sort.html
@@ -0,0 +1,126 @@
+{% extends 'tecnicas/layouts/base.html' %}
+{% load static %}
+
+{% block title %}Monitoreo{% endblock %}
+
+{% block content %}
+
+
+
+ Sesión sensorial en curso
+
+
+
+
+ Código de sesión:
+ {{ code_session }}
+
+
+
+
+
+
+ {% if error %}
+ {% include "../components/error-message.html" with message=error %}
+ {% endif %}
+
+ {% if message %}
+ {% include "../components/error-message.html" with message=message %}
+ {% endif %}
+
+
+
+
+ Actualizar lista
+
+
+ Catadores activos
+
+
+
+
+
+
Participantes
+
+
+
+ Máximo:
+ {{ max_testers }}
+
+
+
+ Actuales:
+
+ {{ current_testers }}
+
+
+ Activos:
+ {{ active_testers }}
+
+
+
+
+
+
+
+
Usuario
+
Nombre
+
Estado
+
Finalizado
+
+
+
+ {% for parti in participations %}
+
+ {{ parti.catador.user.username }}
+
+ {{ parti.catador.user.first_name }}
+ {{ parti.catador.user.last_name}}
+
+
+ {% if parti.activo %}
+ Activo
+ {% else %}
+ No activo
+ {% endif %}
+
+ {% if parti.finalizado %}
+ Si
+ {% else %}
+ No
+ {% endif %}
+
+ {% endfor %}
+
+
+
+
+
+
+{% endblock %}
+
+{% block extra_js %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/tecnicas/templates/tecnicas/manage_sesions/sesiones-panel.html b/tecnicas/templates/tecnicas/manage_sesions/sessions-panel.html
similarity index 100%
rename from tecnicas/templates/tecnicas/manage_sesions/sesiones-panel.html
rename to tecnicas/templates/tecnicas/manage_sesions/sessions-panel.html
diff --git a/tecnicas/templates/tecnicas/manage_tester/catador-crear.html b/tecnicas/templates/tecnicas/manage_tester/tester-create.html
similarity index 100%
rename from tecnicas/templates/tecnicas/manage_tester/catador-crear.html
rename to tecnicas/templates/tecnicas/manage_tester/tester-create.html
diff --git a/tecnicas/templates/tecnicas/manage_tester/catador-lista.html b/tecnicas/templates/tecnicas/manage_tester/tester-list.html
similarity index 100%
rename from tecnicas/templates/tecnicas/manage_tester/catador-lista.html
rename to tecnicas/templates/tecnicas/manage_tester/tester-list.html
diff --git a/tecnicas/templates/tecnicas/manage_tester/catador-buscar.html b/tecnicas/templates/tecnicas/manage_tester/tester-search.html
similarity index 100%
rename from tecnicas/templates/tecnicas/manage_tester/catador-buscar.html
rename to tecnicas/templates/tecnicas/manage_tester/tester-search.html
diff --git a/tecnicas/templates/tecnicas/manage_tester/catadores-panel.html b/tecnicas/templates/tecnicas/manage_tester/testers-panel.html
similarity index 100%
rename from tecnicas/templates/tecnicas/manage_tester/catadores-panel.html
rename to tecnicas/templates/tecnicas/manage_tester/testers-panel.html
diff --git a/tecnicas/templates/tecnicas/manage_vocabulary/create-vocabulary.html b/tecnicas/templates/tecnicas/manage_vocabulary/create-vocabulary.html
index 0019103df764f6bb4b82ec4b7b187593a69c96a0..c21ccecaae9d3173f9195b1ccac0aa9d09041b6b 100644
--- a/tecnicas/templates/tecnicas/manage_vocabulary/create-vocabulary.html
+++ b/tecnicas/templates/tecnicas/manage_vocabulary/create-vocabulary.html
@@ -26,15 +26,9 @@
@@ -105,22 +99,11 @@
- {% if name_vacabulary %}
- {{ name_vacabulary }}
- {% endif %}
-
- {% if name_vacabulary %}
-
- Actualizar vocabulario
-
- {% else %}
Crear vocabulario
- {% endif %}
Cancelar
diff --git a/tecnicas/templates/tecnicas/manage_vocabulary/panel-vocabulary.html b/tecnicas/templates/tecnicas/manage_vocabulary/panel-vocabulary.html
index 253a762e83cbaea5f17fa877d3ef8ad329af6363..e2203a3ea1b544b31594918d2f40b5e9e36b0cd0 100644
--- a/tecnicas/templates/tecnicas/manage_vocabulary/panel-vocabulary.html
+++ b/tecnicas/templates/tecnicas/manage_vocabulary/panel-vocabulary.html
@@ -19,7 +19,7 @@
-
diff --git a/tecnicas/templates/tecnicas/manage_vocabulary/view-vocabulary.html b/tecnicas/templates/tecnicas/manage_vocabulary/view-vocabulary.html
new file mode 100644
index 0000000000000000000000000000000000000000..52a4be84d6f5d0cb7207d67c060bf17ca43b617b
--- /dev/null
+++ b/tecnicas/templates/tecnicas/manage_vocabulary/view-vocabulary.html
@@ -0,0 +1,85 @@
+{% extends 'tecnicas/layouts/base.html' %}
+{% load static %}
+
+{% block title %}Ver Vocabulario{% endblock %}
+
+{% block content %}
+
+
+
+
+
Detalles del Vocabulario
+
+
+
+
+ {% if error %}
+ {% include "../components/error-message.html" with message=error %}
+
+
+
+ {% else %}
+
+
+
+
+
Nombre del Vocabulario:
+
{{ vocabulary.nombre_vocabulario }}
+
+
+
+
Fecha de Creación:
+
{{ vocabulary.creado|date:"d/m/Y H:i" }}
+
+
+
+
Total de Palabras:
+
{{ words|length }}
+
+
+
+
+
+
Palabras del Vocabulario
+
+
+ {% if words %}
+
+ {% for word in words %}
+
+ {{ word.nombre_palabra }}
+
+ {% endfor %}
+
+ {% else %}
+
+
Este vocabulario no tiene palabras asociadas
+
+ {% endif %}
+
+
+
+
+
+ {% endif %}
+
+
+{% endblock %}
diff --git a/tecnicas/templatetags/__init__.py b/tecnicas/templatetags/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tecnicas/templatetags/custom_filters.py b/tecnicas/templatetags/custom_filters.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b04b5c6456e08fae5c2cca82c5663a4c3fb9cf5
--- /dev/null
+++ b/tecnicas/templatetags/custom_filters.py
@@ -0,0 +1,7 @@
+from django import template
+
+register = template.Library()
+
+@register.filter
+def get_item(dictionary, key):
+ return dictionary.get(key)
diff --git a/tecnicas/urls.py b/tecnicas/urls.py
index f8dee7d53ed3d8462fa2cd671aa22678a52135c2..2c058c271ca04cd1ebc2d6fb3fca60ac8caf91e1 100644
--- a/tecnicas/urls.py
+++ b/tecnicas/urls.py
@@ -53,6 +53,10 @@ urlpatterns = [
views.createVocabulary,
name="crear_vocabulario"),
+ path("presenter/ver-vocabulario/",
+ views.viewVocabulary,
+ name="ver_vocabulario"),
+
path("presenter/lista-vocabulario/",
views.listVocabulary,
name="lista_vocabulario"),
@@ -110,6 +114,22 @@ urlpatterns = [
views.convencionalScales,
name="session_convencional"),
+ path("testers/init-session//cata",
+ views.cataTest,
+ name="session_cata"),
+
+ path("testers/init-session//perfil-flash",
+ views.pfTest,
+ name="session_pf"),
+
+ path("testers/init-session//sort",
+ views.sortTest,
+ name="session_sort"),
+
+ path("testers/init-session//nappping",
+ views.nappingTest,
+ name="session_napping"),
+
# APIs
path("presenter/api/nueva-etiqueta",
@@ -124,7 +144,23 @@ urlpatterns = [
views.wordsVocabulary,
name="api_palabras_vocabulary"),
- path("testers/api/ratingword",
- views.reatingWord,
- name="api_rating_word"),
+ path("testers/api/ratingword/escalas",
+ views.ratingWordScales,
+ name="api_rating_word_scalas"),
+
+ path("testers/api/ratingword/cata",
+ views.ratingWordCata,
+ name="api_rating_word_cata"),
+
+ path("testers/api/ratingword/pf/list",
+ views.apiListWordsPF,
+ name="api_rating_word_pf_list"),
+
+ path("testers/api/rating-sort",
+ views.ratingSort,
+ name="api_rating_sort"),
+
+ path("testers/api/rating-napping",
+ views.ratingNapping,
+ name="api_rating_napping"),
]
diff --git a/tecnicas/views/__init__.py b/tecnicas/views/__init__.py
index 517caa7d18822de84228be05eec15659aeff42ce..e8bd6a73813b576a16379f50464cd57648b14634 100644
--- a/tecnicas/views/__init__.py
+++ b/tecnicas/views/__init__.py
@@ -20,15 +20,24 @@ from .tester_management.tester_list import testerList
from .vocabulary_management.vocabulry_menu import vocabularyMenu
from .vocabulary_management.create_vocabulary import createVocabulary
+from .vocabulary_management.view_vocabulary import viewVocabulary
from .vocabulary_management.list_vocabulary import listVocabulary
from .apis.api_tag import newTag
from .apis.api_words import words
from .apis.api_words import wordsVocabulary
-from .apis.rating_word import reatingWord
+from .apis.api_list_words_pf import apiListWordsPF
+from .apis.rating_word_scales import ratingWordScales
+from .apis.rating_word_cata import ratingWordCata
+from .apis.rating_sort import ratingSort
+from .apis.rating_napping import ratingNapping
from .tester_forms.init_tester_form import initTesterForm
-from .tester_forms.convencional_scales import convencionalScales
from .tester_forms.panel_main_tester import mainPanelTester
from .tester_forms.login_session import loginSessionTester
from .tester_forms.sessions_list_tester import sessionsListTester
+from .tester_forms.convencional_scales import convencionalScales
+from .tester_forms.cata_test import cataTest
+from .tester_forms.pf_test import pfTest
+from .tester_forms.sort_test import sortTest
+from .tester_forms.napping_test import nappingTest
diff --git a/tecnicas/views/apis/api_list_words_pf.py b/tecnicas/views/apis/api_list_words_pf.py
new file mode 100644
index 0000000000000000000000000000000000000000..431543c1ac3b447f6b86052e0a908207e5dc8f99
--- /dev/null
+++ b/tecnicas/views/apis/api_list_words_pf.py
@@ -0,0 +1,30 @@
+from django.http import HttpRequest, JsonResponse
+from tecnicas.utils import general_error
+from tecnicas.controllers import RatingPFListController
+import json
+
+
+def apiListWordsPF(req: HttpRequest):
+ if req.method == "GET":
+ return RatingPFListController.getListWords(request=req)
+ elif req.method == "POST":
+ try:
+ data = json.loads(req.body.decode("utf-8"))
+ phase = data.get("phase", [])
+ if phase == 1 or phase == 2:
+ raw_words = data.get("words", [])
+ response = RatingPFListController.saveList(
+ request=req, current_phase=phase, words=raw_words)
+
+ elif phase >= 3:
+ word = data.get("word", [])
+ raw_data = data.get("data", [])
+ response = RatingPFListController.saveRatings(
+ request=req, word_rating=word, data=raw_data)
+
+ return response
+ except Exception as e:
+ print("Error:", e)
+ return JsonResponse({"error": "Error procesando datos"}, status=400)
+ else:
+ return JsonResponse({"error": "Método no permitido"}, status=405)
diff --git a/tecnicas/views/apis/rating_napping.py b/tecnicas/views/apis/rating_napping.py
new file mode 100644
index 0000000000000000000000000000000000000000..5ef98c2ab4b4d613a1f8132c6c55ecc4a77e0c90
--- /dev/null
+++ b/tecnicas/views/apis/rating_napping.py
@@ -0,0 +1,16 @@
+from django.http import HttpRequest, JsonResponse
+from tecnicas.controllers import RatingNappingController
+import json
+
+
+def ratingNapping(req: HttpRequest):
+ if req.method == "POST":
+ try:
+ data = json.loads(req.body.decode("utf-8"))
+ response = RatingNappingController.saveRatingCoordinates(
+ request=req, data=data)
+ return response
+ except Exception as e:
+ return JsonResponse({"error": "Error al procesar datos"})
+ else:
+ return JsonResponse({"error": "Método no permitido"})
diff --git a/tecnicas/views/apis/rating_sort.py b/tecnicas/views/apis/rating_sort.py
new file mode 100644
index 0000000000000000000000000000000000000000..ff6cd36718a52cf8a2569142e9ca70ae4f48fbb8
--- /dev/null
+++ b/tecnicas/views/apis/rating_sort.py
@@ -0,0 +1,16 @@
+from django.http import HttpRequest, JsonResponse
+from tecnicas.controllers import RatingSortController
+import json
+
+
+def ratingSort(req: HttpRequest):
+ if req.method == "POST":
+ try:
+ data = json.loads(req.body.decode("utf-8"))
+ response = RatingSortController.saveRating(
+ request=req, data=data)
+ return response
+ except Exception as e:
+ return JsonResponse({"error": "Error al procesar datos"})
+ else:
+ return JsonResponse({"error": "Método no permitido"})
diff --git a/tecnicas/views/apis/rating_word_cata.py b/tecnicas/views/apis/rating_word_cata.py
new file mode 100644
index 0000000000000000000000000000000000000000..4524703dae0396e344480864f5432b7db6153b3e
--- /dev/null
+++ b/tecnicas/views/apis/rating_word_cata.py
@@ -0,0 +1,21 @@
+from django.http import HttpRequest, JsonResponse
+from tecnicas.utils import general_error
+from tecnicas.controllers import RatingCataController
+import json
+
+
+def ratingWordCata(req: HttpRequest):
+ if req.method == "POST":
+ try:
+ data = json.loads(req.body.decode("utf-8"))
+ raw_words = data.get("words", [])
+ raw_product = data.get("product", [])
+
+ response = RatingCataController.saveRatingWords(
+ request=req, data_words=raw_words, data_prodct=raw_product)
+ return response
+ except Exception as e:
+ print("Error:", e)
+ return JsonResponse({"error": "Error procesando datos"}, status=400)
+ else:
+ return JsonResponse({"error": "Método no permitido"}, status=405)
diff --git a/tecnicas/views/apis/rating_word.py b/tecnicas/views/apis/rating_word_scales.py
similarity index 92%
rename from tecnicas/views/apis/rating_word.py
rename to tecnicas/views/apis/rating_word_scales.py
index a142a7dd0823188241e4d7e217bae3b4c8fcea86..98f5ea70c64979f1a52919b4e6c87441b246c512 100644
--- a/tecnicas/views/apis/rating_word.py
+++ b/tecnicas/views/apis/rating_word_scales.py
@@ -28,12 +28,12 @@
* Calquier otro metodo que se maneje mandar un error
'''
from django.http import HttpRequest, JsonResponse
-from tecnicas.controllers import ApiRatingController, CalificacionController, DatoController
+from tecnicas.controllers import RatingScalesController, CalificacionController, DatoController
from tecnicas.utils import general_error
import json
-def reatingWord(req: HttpRequest):
+def ratingWordScales(req: HttpRequest):
if req.method == "POST":
if not req.POST["rating-word"] or not req.POST["id-word"] or not req.POST["id-product"]:
return JsonResponse({"error": "No se mandó información necesaria para la calificación"})
@@ -43,7 +43,7 @@ def reatingWord(req: HttpRequest):
received_id_product = json.loads(req.POST.get("id-product"))
id_technique = json.loads(req.POST.get("id-technique"))
- view_controller = ApiRatingController(
+ view_controller = RatingScalesController(
rating_controller=CalificacionController(
technique=id_technique,
product=received_id_product,
@@ -60,4 +60,4 @@ def reatingWord(req: HttpRequest):
return JsonResponse(response_data)
else:
- return general_error("No puede usar este método aquí")
+ return general_error("No puede usar este método aquí")
\ No newline at end of file
diff --git a/tecnicas/views/sessions_config/configuration_panel_basic.py b/tecnicas/views/sessions_config/configuration_panel_basic.py
index d03f2a693a27e9855a01fc10668efdd37d1dceb3..fc0f9f9632e3534d9a0bbe895cd94de11a98792c 100644
--- a/tecnicas/views/sessions_config/configuration_panel_basic.py
+++ b/tecnicas/views/sessions_config/configuration_panel_basic.py
@@ -17,6 +17,12 @@ def configurationPanelBasic(req: HttpRequest):
response = PanelBasicController.controllGetRATA(request=req)
elif name_tecnica == "cata":
response = PanelBasicController.controllGetCATA(request=req)
+ elif name_tecnica == "perfil flash":
+ response = PanelBasicController.controllGetPF(request=req)
+ elif name_tecnica == "sort":
+ response = PanelBasicController.controllGetSort(request=req)
+ elif name_tecnica == "napping":
+ response = PanelBasicController.controllGetNapping(request=req)
else:
response = redirect(
reverse("cata_system:seleccion_tecnica") + "?error=Técnica no valida o sin implementar")
@@ -34,6 +40,15 @@ def configurationPanelBasic(req: HttpRequest):
elif name_tecnica == "cata":
response = PanelBasicController.controllPostCATA(
request=req, name_tecnica=name_tecnica)
+ elif name_tecnica == "perfil flash":
+ response = PanelBasicController.controllPostPF(
+ request=req, name_tecnica=name_tecnica)
+ elif name_tecnica == "sort":
+ response = PanelBasicController.controllPostSort(
+ request=req, name_tecnica=name_tecnica)
+ elif name_tecnica == "napping":
+ response = PanelBasicController.controllPostNapping(
+ request=req, name_tecnica=name_tecnica)
else:
response = redirect(
reverse("cata_system:seleccion_tecnica") + "?error=¡Oh, vaya! Cambio de técnica repentino, vuelve a elegir otra vez")
diff --git a/tecnicas/views/sessions_config/configuration_panel_codes.py b/tecnicas/views/sessions_config/configuration_panel_codes.py
index 93ff4913e129ab05be1e5549f98e690a823c1a16..d68d10e94ee79a90ea35b5a1886c453183db5db0 100644
--- a/tecnicas/views/sessions_config/configuration_panel_codes.py
+++ b/tecnicas/views/sessions_config/configuration_panel_codes.py
@@ -18,12 +18,9 @@ def configurationPanelCodes(req: HttpRequest):
if name_technique == "escalas":
response = PanelCodesController.controllGetEscalas(
req, data_basic)
- elif name_technique == "rata":
- response = PanelCodesController.controllGetRATA(
- req, data_basic)
- elif name_technique == "cata":
- response = PanelCodesController.controllGetCATA(
- req, data_basic)
+ elif name_technique in ["rata", "cata", "perfil flash", "sort", "napping"]:
+ response = PanelCodesController.controllGetWithoutOrders(
+ request=req, data=data_basic, name_technique=name_technique)
else:
response = redirect(
reverse("cata_system:seleccion_tecnica") + "?error=Técnica no valida")
@@ -33,11 +30,12 @@ def configurationPanelCodes(req: HttpRequest):
if name_technique == "escalas":
response = PanelCodesController.controllPostEscalas(
req, data_basic)
- elif name_technique == "rata":
- response = PanelCodesController.controllPostRATA(request=req)
- elif name_technique == "cata":
- response = PanelCodesController.controllPostRATA(
- request=req, is_rata=False)
+ elif name_technique in ["rata", "cata"]:
+ response = PanelCodesController.controllPostWithWords(
+ request=req, name_technique=name_technique)
+ elif name_technique in ["perfil flash", "sort", "napping"]:
+ response = PanelCodesController.controllPostWithoutOrdersWords(
+ request=req, name_technique=name_technique)
else:
response = redirect(
reverse("cata_system:seleccion_tecnica") + "?error=Técnica no valida")
diff --git a/tecnicas/views/sessions_config/configuration_panel_tags.py b/tecnicas/views/sessions_config/configuration_panel_tags.py
index 4c727cdc7f25b3ccafad5c31daf55c9106e2d402..ece28c43ed1e95d61c7ec0ea3f276d186bf168f9 100644
--- a/tecnicas/views/sessions_config/configuration_panel_tags.py
+++ b/tecnicas/views/sessions_config/configuration_panel_tags.py
@@ -12,9 +12,10 @@ def configurationPanelTags(req: HttpRequest):
"?error=datos requeridos no encontrados")
basic_data = req.session.get("form_basic")
+ name_technique = basic_data["name_tecnica"]
if req.method == "GET":
- if basic_data["name_tecnica"] == "escalas" or basic_data["name_tecnica"] == "rata":
+ if name_technique == "escalas" or name_technique == "rata" or name_technique == "perfil flash":
response = PanelTagsController.controllGetEscalas(
request=req, data=basic_data)
else:
@@ -23,7 +24,7 @@ def configurationPanelTags(req: HttpRequest):
return response
elif req.method == "POST":
- if basic_data["name_tecnica"] == "escalas" or basic_data["name_tecnica"] == "rata":
+ if name_technique == "escalas" or name_technique == "rata" or name_technique == "perfil flash":
response = PanelTagsController.controllPostEscalas(
request=req, data=basic_data)
else:
diff --git a/tecnicas/views/sessions_config/configuration_panel_words.py b/tecnicas/views/sessions_config/configuration_panel_words.py
index 2898f6309b1e933d44e06d1980edc9a95cf6ce66..5f6d7c661f385ff56ef7838fe8b60801a38566e6 100644
--- a/tecnicas/views/sessions_config/configuration_panel_words.py
+++ b/tecnicas/views/sessions_config/configuration_panel_words.py
@@ -14,12 +14,10 @@ def configurationPanelWords(req: HttpRequest):
basic_data = req.session["form_basic"]
name_technique = basic_data["name_tecnica"]
- style_words = EstiloPalabra.objects.get(
- id=basic_data["estilo_palabras"]).nombre_estilo
+ style_words = basic_data["estilo_palabras"]
if req.method == "GET":
if name_technique == "escalas" or name_technique == "rata" or name_technique == "cata":
- print()
if style_words == "atributos":
response = PanelWordsController.controllGetEscalasAtributes(
req)
diff --git a/tecnicas/views/sessions_config/create_session.py b/tecnicas/views/sessions_config/create_session.py
index b9f9371757277bc19918d56da33c33a70938147f..7e6fce964faf7f750f1505c8a9ffc6f00ee4f243 100644
--- a/tecnicas/views/sessions_config/create_session.py
+++ b/tecnicas/views/sessions_config/create_session.py
@@ -1,7 +1,7 @@
from django.http import HttpRequest, JsonResponse
from django.shortcuts import render, redirect
from django.urls import reverse
-from tecnicas.controllers import PanelCreateController
+from tecnicas.controllers import PanelCreateEscalasController, PanelCreateRataController, PanelCreateCataController, PanelCreatePFController, PanelCreateSortController, PanelCreateNappingController
from tecnicas.utils import deleteDataSession
@@ -15,8 +15,18 @@ def createSession(req: HttpRequest):
name_technique = basic_data["name_tecnica"]
if req.method == "GET":
- if name_technique == "escalas" or name_technique == "rata" or name_technique == "cata":
- response = PanelCreateController.controllGetEscalas(req)
+ if name_technique == "escalas":
+ response = PanelCreateEscalasController.controllGet(req)
+ elif name_technique == "rata":
+ response = PanelCreateRataController.controllGet(req)
+ elif name_technique == "cata":
+ response = PanelCreateCataController.controllGet(req)
+ elif name_technique == "perfil flash":
+ response = PanelCreatePFController.controllGet(req)
+ elif name_technique == "sort":
+ response = PanelCreateSortController.controllGet(req)
+ elif name_technique == "napping":
+ response = PanelCreateNappingController.controllGet(req)
else:
response = redirect(
reverse("cata_system:seleccion_tecnica") + "?error=Técnica no valida")
@@ -24,11 +34,17 @@ def createSession(req: HttpRequest):
return response
if req.method == "POST":
if name_technique == "escalas":
- response = PanelCreateController.controllPostEscalas(req)
+ response = PanelCreateEscalasController.controllPost(req)
elif name_technique == "rata":
- response = PanelCreateController.controllPostRATA(req)
+ response = PanelCreateRataController.controllPost(req)
elif name_technique == "cata":
- response = PanelCreateController.controllPostCATA(req)
+ response = PanelCreateCataController.controllPost(req)
+ elif name_technique == "perfil flash":
+ response = PanelCreatePFController.controllPost(req)
+ elif name_technique == "sort":
+ response = PanelCreateSortController.controllPost(req)
+ elif name_technique == "napping":
+ response = PanelCreateNappingController.controllPost(req)
else:
response = redirect(
reverse("cata_system:seleccion_tecnica") + "?error=Técnica no valida")
diff --git a/tecnicas/views/sessions_config/seleccion_tecnica.py b/tecnicas/views/sessions_config/seleccion_tecnica.py
index 3ddd04b309b1be527b98e6e2fee7f9ffebf93dce..e8f09605613f02ef029567e1f95433a3c25467e9 100644
--- a/tecnicas/views/sessions_config/seleccion_tecnica.py
+++ b/tecnicas/views/sessions_config/seleccion_tecnica.py
@@ -9,6 +9,6 @@ def selecionTecnica(req:HttpRequest):
error = error.replace("_", " ")
error = error.capitalize()
- return render(req, "tecnicas/create_sesion/seleccion-tecnica.html", context={"tipos":tipos, "error":error})
+ return render(req, "tecnicas/create_sesion/select-tecnica.html", context={"tipos":tipos, "error":error})
except KeyError:
- return render(req, "tecnicas/create_sesion/seleccion-tecnica.html", context={"tipos":tipos})
\ No newline at end of file
+ return render(req, "tecnicas/create_sesion/select-tecnica.html", context={"tipos":tipos})
\ No newline at end of file
diff --git a/tecnicas/views/sessions_management/session_details.py b/tecnicas/views/sessions_management/session_details.py
index f292989ee61e4d5964ccfa443e7d0503f350620d..8c7a4f38b108fd2b01cf4929a277bf607f3eb09e 100644
--- a/tecnicas/views/sessions_management/session_details.py
+++ b/tecnicas/views/sessions_management/session_details.py
@@ -3,7 +3,7 @@ from django.shortcuts import redirect
from django.urls import reverse
from tecnicas.models import SesionSensorial
from tecnicas.utils import noValidTechnique
-from ...controllers import DetallesEscalasController
+from tecnicas.controllers import DetallesController, DetallesEscalasController, DetallesCATAController, DetallesPFController, DetallesSortController, DetallesNappingController
def sessionDetails(req: HttpRequest, session_code: str):
@@ -13,15 +13,42 @@ def sessionDetails(req: HttpRequest, session_code: str):
else:
message = ""
- sensorial_session = SesionSensorial.objects.get(
- codigo_sesion=session_code)
+ try:
+ sensorial_session = SesionSensorial.objects.get(
+ codigo_sesion=session_code)
+ except SesionSensorial.DoesNotExist:
+ return noValidTechnique(params={"page": 1}, query_params={"message": "Sesión no encontrada"}, name_view="cata_system:panel_sesiones")
+
use_techinique = sensorial_session.tecnica.tipo_tecnica.nombre_tecnica
if use_techinique == "escalas" or use_techinique == "rata":
controller_view = DetallesEscalasController(
session=sensorial_session)
- response = controller_view.getResponse(
+ response = controller_view.controllGetResponse(
+ request=req, message=message)
+
+ elif use_techinique == "cata":
+ controller_view = DetallesCATAController(
+ session=sensorial_session)
+ response = controller_view.controllGetResponse(
request=req, message=message)
+
+ elif use_techinique == "perfil flash":
+ controller_view = DetallesPFController(session=sensorial_session)
+ response = controller_view.controllGetResponse(
+ request=req, message=message)
+
+ elif use_techinique == "sort":
+ controller_view = DetallesSortController(session=sensorial_session)
+ response = controller_view.controllGetResponse(
+ request=req, message=message)
+
+ elif use_techinique == "napping":
+ controller_view = DetallesNappingController(
+ session=sensorial_session)
+ response = controller_view.controllGetResponse(
+ request=req, message=message)
+
else:
response = noValidTechnique(
params={"page": 1},
@@ -30,28 +57,38 @@ def sessionDetails(req: HttpRequest, session_code: str):
},
name_view="cata_system:panel_sesiones"
)
-
+
return response
+
elif req.method == "POST":
sensorial_session = SesionSensorial.objects.get(
codigo_sesion=session_code)
+
use_techinique = sensorial_session.tecnica.tipo_tecnica.nombre_tecnica
+ controller_view = DetallesController(sensorial_session)
- if use_techinique == "escalas" or use_techinique == "rata":
- controller_view = DetallesEscalasController(sensorial_session)
+ action = req.POST.get("action")
- if req.POST["action"] == "start_session":
+ if use_techinique in ["escalas", "rata", "cata", "perfil flash", "sort"]:
+ if action == "start_session":
response = controller_view.startRepetition(
presenter=req.user.user_presentador, request=req)
- elif req.POST.get("action") == "delete_session":
+
+ elif action == "delete_session":
controller_view.deleteSesorialSession()
response = redirect(
reverse("cata_system:panel_sesiones", kwargs={"page": 1}))
- else:
- response = controller_view.getResponse(
- error="No se reconoce la acción a realizar")
+
+ elif use_techinique == "napping":
+ controller_view = DetallesNappingController(
+ session=sensorial_session)
+
+ response = controller_view.controllPostResponse(
+ request=req, action=action)
+
else:
- response = noValidTechnique()
+ response = controller_view.controllGetResponse(
+ error="No se reconoce la acción a realizar")
return response
else:
diff --git a/tecnicas/views/sessions_management/session_monitor.py b/tecnicas/views/sessions_management/session_monitor.py
index 515f26b29af529c2edb747eb6b1e289f2ae22d04..3cddfc607461d23086687aec758ec7869914aba0 100644
--- a/tecnicas/views/sessions_management/session_monitor.py
+++ b/tecnicas/views/sessions_management/session_monitor.py
@@ -3,12 +3,11 @@ Para finalizar la sesion se debe realizar lo siguiente
# Obtener todas las participaciones
'''
-
from django.http import HttpRequest, JsonResponse
from django.shortcuts import render, redirect
from django.urls import reverse
from tecnicas.models import SesionSensorial
-from tecnicas.controllers import MonitorEscalasController, MonitorRATAController
+from tecnicas.controllers import MonitorEscalasController, MonitorRATAController, MonitorPFController, MonitorSortController, MonitorNappingController
from tecnicas.utils import noValidTechnique
@@ -18,9 +17,22 @@ def sessionMonitor(req: HttpRequest, session_code: str):
codigo_sesion=session_code)
use_techinique = sensorial_session.tecnica.tipo_tecnica.nombre_tecnica
- if use_techinique == "escalas" or use_techinique == "rata":
+ if use_techinique in ["escalas", "rata", "cata"]:
controll_view = MonitorEscalasController(sensorial_session)
- response = controll_view.controlGetResponse(request=req)
+ response = controll_view.controllGetResponse(request=req)
+
+ elif use_techinique == "perfil flash":
+ controll_view = MonitorPFController(sensorial_session)
+ response = controll_view.controllGetResponse(request=req)
+
+ elif use_techinique == "sort":
+ controll_view = MonitorSortController(sensorial_session)
+ response = controll_view.controllGetResponse(request=req)
+
+ elif use_techinique == "napping":
+ controll_view = MonitorNappingController(sensorial_session)
+ response = controll_view.controllGetResponse(request=req)
+
else:
response = noValidTechnique(
params={
@@ -31,7 +43,6 @@ def sessionMonitor(req: HttpRequest, session_code: str):
},
name_view="cata_system:detalles_sesion"
)
-
return response
elif req.method == "POST":
sensorial_session = SesionSensorial.objects.get(
@@ -48,7 +59,8 @@ def sessionMonitor(req: HttpRequest, session_code: str):
else:
response = controll_view.controlGetResponse(
request=req, error="No se ha definido la acción a realizar")
- elif use_techinique == "rata":
+
+ elif use_techinique == "rata" or use_techinique == "cata":
controll_view = MonitorRATAController(sensorial_session)
action = req.POST["action"]
@@ -58,6 +70,40 @@ def sessionMonitor(req: HttpRequest, session_code: str):
else:
response = controll_view.controlGetResponse(
request=req, error="No se ha definido la acción a realizar")
+
+ elif use_techinique == "perfil flash":
+ controll_view = MonitorPFController(sensorial_session)
+ action = req.POST["action"]
+
+ if action == "finish_session":
+ response = controll_view.controllPostFinishSession(
+ request=req)
+ else:
+ response = controll_view.controlGetResponse(
+ request=req, error="No se ha definido la acción a realizar")
+
+ elif use_techinique == "sort":
+ controll_view = MonitorSortController(sensorial_session)
+ action = req.POST["action"]
+
+ if action == "finish_session":
+ response = controll_view.controllPostFinishSession(
+ request=req)
+ else:
+ response = controll_view.controlGetResponse(
+ request=req, error="No se ha definido la acción a realizar")
+
+ elif use_techinique == "napping":
+ controll_view = MonitorNappingController(sensorial_session)
+ action = req.POST["action"]
+
+ if action == "finish_session":
+ response = controll_view.controllPostFinishSession(
+ request=req)
+ else:
+ response = controll_view.controlGetResponse(
+ request=req, error="No se ha definido la acción a realizar")
+
else:
response = noValidTechnique(
params={
diff --git a/tecnicas/views/sessions_management/sessions_panel.py b/tecnicas/views/sessions_management/sessions_panel.py
index c64ed878cf2cffd5bdbaf17bbbc6508995789330..690477e7763290161f7e430baa88a997f210fa12 100644
--- a/tecnicas/views/sessions_management/sessions_panel.py
+++ b/tecnicas/views/sessions_management/sessions_panel.py
@@ -12,7 +12,7 @@ def sesionsPanel(req: HttpRequest, page: int):
if isinstance(response, dict):
context["error"] = response["error"]
- return render(req, "tecnicas/manage_sesions/sesiones-panel.html", context=context)
+ return render(req, "tecnicas/manage_sesions/sessions-panel.html", context=context)
(sessions_in_page, is_last_page, current_page) = response
@@ -22,6 +22,6 @@ def sesionsPanel(req: HttpRequest, page: int):
if "message" in req.GET:
context["message"] = req.GET.get("message")
- return render(req, "tecnicas/manage_sesions/sesiones-panel.html", context=context)
+ return render(req, "tecnicas/manage_sesions/sessions-panel.html", context=context)
else:
return JsonResponse({"message": "Método no permitido"})
diff --git a/tecnicas/views/tester_forms/cata_test.py b/tecnicas/views/tester_forms/cata_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..9e857e3b8093703b146c71571da58082e5a9df59
--- /dev/null
+++ b/tecnicas/views/tester_forms/cata_test.py
@@ -0,0 +1,11 @@
+from django.http import HttpRequest
+from tecnicas.models import SesionSensorial
+from tecnicas.controllers import TestCataController
+
+
+def cataTest(req: HttpRequest, code_sesion: str):
+ if req.method == "GET":
+ session = SesionSensorial.objects.get(codigo_sesion=code_sesion)
+ controll_view = TestCataController(
+ sensorial_session=session, user_tester=req.user.user_catador)
+ return controll_view.controllGet(request=req)
diff --git a/tecnicas/views/tester_forms/convencional_scales.py b/tecnicas/views/tester_forms/convencional_scales.py
index 7b96ccf80cc28440f313cd18b2978ea4c924b705..06eb8035bbd16134248f67dfca3d7b7cf81f11c0 100644
--- a/tecnicas/views/tester_forms/convencional_scales.py
+++ b/tecnicas/views/tester_forms/convencional_scales.py
@@ -55,21 +55,26 @@
- Cata segmento en el que se divide debe tener la etiqueda correspondiente por debajo
'''
from django.http import HttpRequest
-from tecnicas.controllers import SesionController, ConvencionalScalesController
+from tecnicas.models import SesionSensorial
+from tecnicas.controllers import TestRataController, TestScalesController
from tecnicas.utils import noValidTechnique
def convencionalScales(req: HttpRequest, code_sesion: str):
- session = SesionController.getSessionByCode(code_sesion)
+ session = SesionSensorial.objects.get(codigo_sesion=code_sesion)
type_technique = session.tecnica.tipo_tecnica.nombre_tecnica
if req.method == "GET":
- view_controller = ConvencionalScalesController(
- sensorial_session=session, user_tester=req.user.user_catador)
if type_technique == "escalas":
- respose = view_controller.controllGetEscalas(request=req)
+ view_controller = TestScalesController(
+ sensorial_session=session, user_tester=req.user.user_catador)
+ respose = view_controller.controllGet(request=req)
+
elif type_technique == "rata":
- respose = view_controller.controllGetRATA(request=req)
+ view_controller = TestRataController(
+ sensorial_session=session, user_tester=req.user.user_catador)
+ respose = view_controller.controllGet(request=req)
+
else:
respose = noValidTechnique(
name_view='cata_system:catador_init_session',
diff --git a/tecnicas/views/tester_forms/init_tester_form.py b/tecnicas/views/tester_forms/init_tester_form.py
index 6fc17607a93660642331df867e119773d67f22b5..9f74a6b922c0ec5401239d18e352357685242dcd 100644
--- a/tecnicas/views/tester_forms/init_tester_form.py
+++ b/tecnicas/views/tester_forms/init_tester_form.py
@@ -1,35 +1,71 @@
from django.http import HttpRequest, JsonResponse
-from django.shortcuts import render, redirect
-from django.urls import reverse
-from tecnicas.controllers import InitSessionTesterController, ParticipacionController
+from django.shortcuts import render
+from tecnicas.controllers import InitSessionEscalasController, InitSessionRATAController, InitSessionPFController, InitSessionSortController, InitSessionNappingController
from tecnicas.models import SesionSensorial
def initTesterForm(req: HttpRequest, code_sesion: str):
session = SesionSensorial.objects.get(codigo_sesion=code_sesion)
type_technique = session.tecnica.tipo_tecnica.nombre_tecnica
- template_url = "tecnicas/forms_tester/init_session.html"
-
- view_controller = InitSessionTesterController(
- sensorial_session=session, user_tester=req.user.user_catador)
+ template_url = "tecnicas/forms_tester/init_scales_test.html"
if req.method == "GET":
if type_technique == "escalas":
- response = view_controller.controllGetEscalas(request=req)
- elif type_technique == "rata":
- response = view_controller.controllGetRATA(request=req)
+ view_controller = InitSessionEscalasController(
+ sensorial_session=session, user_tester=req.user.user_catador)
+ response = view_controller.controllGet(request=req)
+
+ elif type_technique == "rata" or type_technique == "cata":
+ view_controller = InitSessionRATAController(
+ sensorial_session=session, user_tester=req.user.user_catador)
+ response = view_controller.controllGet(request=req)
+
+ elif type_technique == "perfil flash":
+ view_controller = InitSessionPFController(
+ sensorial_session=session, user_tester=req.user.user_catador)
+ response = view_controller.controllGet(request=req)
+
+ elif type_technique == "sort":
+ view_controller = InitSessionSortController(
+ sensorial_session=session, user_tester=req.user.user_catador)
+ response = view_controller.controllGet(request=req)
+
+ elif type_technique == "napping":
+ view_controller = InitSessionNappingController(
+ sensorial_session=session, user_tester=req.user.user_catador)
+ response = view_controller.controllGet(request=req)
+
else:
context = {
"session": session,
- "error": "La técnica usada en esta sesión o ha sido implementada para ingresar a ella"
+ "error": "La técnica usada en esta sesión no ha sido implementada para ingresar a ella"
}
response = render(
req, template_url, context)
return response
+
elif req.method == "POST":
- if type_technique == "escalas" or type_technique == "rata":
- response = view_controller.controllPostEscalas(request=req)
+ if type_technique in ["escalas", "rata", "cata"]:
+ view_controller = InitSessionEscalasController(
+ sensorial_session=session, user_tester=req.user.user_catador)
+ response = view_controller.controllPost(request=req)
+
+ elif type_technique == "perfil flash":
+ view_controller = InitSessionPFController(
+ sensorial_session=session, user_tester=req.user.user_catador)
+ response = view_controller.controllPost(request=req)
+
+ elif type_technique == "sort":
+ view_controller = InitSessionSortController(
+ sensorial_session=session, user_tester=req.user.user_catador)
+ response = view_controller.controllPost(request=req)
+
+ elif type_technique == "napping":
+ view_controller = InitSessionNappingController(
+ sensorial_session=session, user_tester=req.user.user_catador)
+ response = view_controller.controllPost(request=req)
+
else:
context = {
"session": session,
diff --git a/tecnicas/views/tester_forms/login_session.py b/tecnicas/views/tester_forms/login_session.py
index 94f004ef86a99337aa43b37e12897072ecd1981f..4fa8ea32e69a135a6e9ad7e77f564d0868700b54 100644
--- a/tecnicas/views/tester_forms/login_session.py
+++ b/tecnicas/views/tester_forms/login_session.py
@@ -27,10 +27,15 @@ def loginSessionTester(req: HttpRequest):
type_technique = session.tecnica.tipo_tecnica.nombre_tecnica
- if type_technique == "escalas":
- response = login_controller.validateEntryEscalas(request=req)
- elif type_technique == "rata":
- response = login_controller.validateEntryRATA(request=req)
+ if type_technique in ["escalas", "perfil flash", "sort"]:
+ response = login_controller.validateEntryLimitTesters(request=req)
+
+ elif type_technique in ["rata", "cata"]:
+ response = login_controller.validateEntryRataCata(request=req)
+
+ elif type_technique == "napping":
+ response = login_controller.validateEntryNapping(request=req)
+
else:
context = {
"error": "La técnica usada en esta sesión es invalida o no ha sido implementada para ingresar a ella"
diff --git a/tecnicas/views/tester_forms/napping_test.py b/tecnicas/views/tester_forms/napping_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..61cdbd6ec681062693544e0dd47c167da77e55d7
--- /dev/null
+++ b/tecnicas/views/tester_forms/napping_test.py
@@ -0,0 +1,27 @@
+from django.http import HttpRequest
+from tecnicas.models import SesionSensorial
+from tecnicas.controllers import TestNappingController
+from tecnicas.utils import noValidTechnique
+
+
+def nappingTest(req: HttpRequest, code_sesion: str):
+ if req.method == "GET":
+ session = SesionSensorial.objects.get(codigo_sesion=code_sesion)
+ controll_view = TestNappingController(
+ sensorial_session=session, user_tester=req.user.user_catador)
+ return controll_view.controllGet(request=req)
+
+ elif req.method == "POST":
+ # Se usa para finalizar la sesión
+ session = SesionSensorial.objects.get(codigo_sesion=code_sesion)
+ controll_view = TestNappingController(
+ sensorial_session=session, user_tester=req.user.user_catador)
+ return controll_view.controllPost(request=req)
+
+ else:
+ return noValidTechnique(
+ name_view="cata_system:catador_init_session",
+ query_params={
+ "error": "Método no valido"
+ }
+ )
diff --git a/tecnicas/views/tester_forms/pf_test.py b/tecnicas/views/tester_forms/pf_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..32d6f762a4618e478f87285f2d7fe27c316722c9
--- /dev/null
+++ b/tecnicas/views/tester_forms/pf_test.py
@@ -0,0 +1,27 @@
+from django.http import HttpRequest, JsonResponse
+from django.shortcuts import redirect
+from django.urls import reverse
+from tecnicas.models import SesionSensorial
+from tecnicas.controllers import TestPFController, ParticipacionController
+
+
+def pfTest(req: HttpRequest, code_sesion: str):
+ if req.method == "GET":
+ session = SesionSensorial.objects.get(codigo_sesion=code_sesion)
+ controll_view = TestPFController(
+ sensorial_session=session, user_tester=req.user.user_catador)
+ return controll_view.controllGet(request=req)
+
+ elif req.method == "POST":
+ session = SesionSensorial.objects.get(codigo_sesion=code_sesion)
+ controll_view = TestPFController(
+ sensorial_session=session, user_tester=req.user.user_catador)
+ return controll_view.controllPost(request=req)
+
+ else:
+ return noValidTechnique(
+ name_view="cata_system:catador_init_session",
+ query_params={
+ "error": "Método no valido"
+ }
+ )
diff --git a/tecnicas/views/tester_forms/sort_test.py b/tecnicas/views/tester_forms/sort_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..3169302db67be6913e8376b28a0f7214cb889cc1
--- /dev/null
+++ b/tecnicas/views/tester_forms/sort_test.py
@@ -0,0 +1,26 @@
+from django.http import HttpRequest
+from tecnicas.models import SesionSensorial
+from tecnicas.controllers import TestSortController
+from tecnicas.utils import noValidTechnique
+
+
+def sortTest(req: HttpRequest, code_sesion: str):
+ if req.method == "GET":
+ session = SesionSensorial.objects.get(codigo_sesion=code_sesion)
+ controll_view = TestSortController(
+ sensorial_session=session, user_tester=req.user.user_catador)
+ return controll_view.controllGet(request=req)
+
+ elif req.method == "POST":
+ session = SesionSensorial.objects.get(codigo_sesion=code_sesion)
+ controll_view = TestSortController(
+ sensorial_session=session, user_tester=req.user.user_catador)
+ return controll_view.controllPost(request=req)
+
+ else:
+ return noValidTechnique(
+ name_view="cata_system:catador_init_session",
+ query_params={
+ "error": "Método no valido"
+ }
+ )
diff --git a/tecnicas/views/tester_management/tester_create.py b/tecnicas/views/tester_management/tester_create.py
index d5f61bdb86f0acd3325afc817187fe7cec45438d..9ae8e8cb2b347db2686c734222c31835b6ffd44f 100644
--- a/tecnicas/views/tester_management/tester_create.py
+++ b/tecnicas/views/tester_management/tester_create.py
@@ -8,6 +8,8 @@ from tecnicas.forms import CatadorForm
def testerCreate(req: HttpRequest):
+ url_template = "tecnicas/manage_tester/tester-create.html"
+
if req.method == "GET":
form_tester = CatadorForm()
@@ -15,7 +17,7 @@ def testerCreate(req: HttpRequest):
"form_cata": form_tester
}
- return render(req, "tecnicas/manage_tester/catador-crear.html", context)
+ return render(req, url_template, context)
elif req.method == "POST":
new_values = {}
@@ -51,10 +53,10 @@ def testerCreate(req: HttpRequest):
)
except (ValidationError, DatabaseError):
context["error"] = "nombre de usuario en uso"
- return render(req, "tecnicas/manage_tester/catador-crear.html", context)
+ return render(req, url_template, context)
context["message"] = "Datos guardados, consúltelo en Listar Catadores"
context["form_cata"] = CatadorForm()
- return render(req, "tecnicas/manage_tester/catador-crear.html", context)
+ return render(req, url_template, context)
else:
context["error"] = "Datos no validos"
- return render(req, "tecnicas/manage_tester/catador-crear.html", context)
+ return render(req, url_template, context)
diff --git a/tecnicas/views/tester_management/tester_list.py b/tecnicas/views/tester_management/tester_list.py
index a7be62c98b2e7bbff70a98e5d8b8b9a7a7d34812..c16b527a9683541a68fc2e55547d596880c791e4 100644
--- a/tecnicas/views/tester_management/tester_list.py
+++ b/tecnicas/views/tester_management/tester_list.py
@@ -8,6 +8,6 @@ def testerList(req: HttpRequest, num_page: int):
if req.method == "GET":
view_controller = TesterListController(page=num_page)
view_context = view_controller.getContext()
- return render(req, "tecnicas/manage_tester/catador-lista.html", view_context)
+ return render(req, "tecnicas/manage_tester/tester-list.html", view_context)
else:
return general_error("Método no permitido")
diff --git a/tecnicas/views/tester_management/tester_menu.py b/tecnicas/views/tester_management/tester_menu.py
index c36f677119d7a2ec19304d4a00e73266a0b189cc..a86adc059155e7c116d88d266b45427694fe4e28 100644
--- a/tecnicas/views/tester_management/tester_menu.py
+++ b/tecnicas/views/tester_management/tester_menu.py
@@ -1,4 +1,4 @@
from django.shortcuts import render
def testerMenu(req):
- return render(req, "tecnicas/manage_tester/catadores-panel.html")
\ No newline at end of file
+ return render(req, "tecnicas/manage_tester/testers-panel.html")
\ No newline at end of file
diff --git a/tecnicas/views/tester_management/tester_search.py b/tecnicas/views/tester_management/tester_search.py
index 9305fda489f064c9c928cfc1c4f0f0b3721e2f1c..d55dcaab5004767a58ad256fd85c339145e0b5f6 100644
--- a/tecnicas/views/tester_management/tester_search.py
+++ b/tecnicas/views/tester_management/tester_search.py
@@ -8,13 +8,14 @@ from tecnicas.models import Catador
def testerSearch(req: HttpRequest):
+ url_template = "tecnicas/manage_tester/tester-search.html"
if req.method == "GET":
context = {}
if "user" in req.GET:
username = req.GET["user"]
else:
- return render(req, "tecnicas/manage_tester/catador-buscar.html")
+ return render(req, url_template)
try:
tester = Catador.objects.get(user__username=username)
@@ -30,7 +31,7 @@ def testerSearch(req: HttpRequest):
except Catador.DoesNotExist:
context["error"] = "usuario no encontrado"
- return render(req, "tecnicas/manage_tester/catador-buscar.html", context)
+ return render(req, url_template, context)
elif req.method == "POST":
context = {}
@@ -63,10 +64,10 @@ def testerSearch(req: HttpRequest):
tester.save()
except (ValidationError, DatabaseError):
context["error"] = "nombre de usuario en uso"
- return render(req, "tecnicas/manage_tester/catador-crear.html", context)
+ return render(req, url_template, context)
context["message"] = "Datos actualizados, consúltelo en Listar Catadores"
context["form_cata"] = form_tester
- return render(req, "tecnicas/manage_tester/catador-crear.html", context)
+ return render(req, url_template, context)
else:
context["error"] = "Datos no validos"
- return render(req, "tecnicas/manage_tester/catador-crear.html", context)
+ return render(req, url_template, context)
diff --git a/tecnicas/views/vocabulary_management/view_vocabulary.py b/tecnicas/views/vocabulary_management/view_vocabulary.py
new file mode 100644
index 0000000000000000000000000000000000000000..59a74b7ac03b3e21c4dcd78574c12a24ad04d731
--- /dev/null
+++ b/tecnicas/views/vocabulary_management/view_vocabulary.py
@@ -0,0 +1,13 @@
+from django.shortcuts import render
+from django.http import HttpRequest
+from tecnicas.controllers import ViewVocabularyController
+
+
+def viewVocabulary(req: HttpRequest, nombre_vocabulario: str):
+ view_controller = ViewVocabularyController()
+ if req.method == "GET":
+ response = view_controller.controllGet(req, nombre_vocabulario)
+ return response
+ else:
+ context = {"error": "Método no permitido"}
+ return render(req, "tecnicas/manage_vocabulary/view-vocabulary.html", context)