iatoolkit 0.59.1__py3-none-any.whl → 0.67.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of iatoolkit might be problematic. Click here for more details.

Files changed (93) hide show
  1. iatoolkit/__init__.py +2 -0
  2. iatoolkit/base_company.py +2 -19
  3. iatoolkit/common/routes.py +28 -25
  4. iatoolkit/common/session_manager.py +2 -0
  5. iatoolkit/common/util.py +17 -6
  6. iatoolkit/company_registry.py +1 -2
  7. iatoolkit/iatoolkit.py +54 -20
  8. iatoolkit/locales/en.yaml +167 -0
  9. iatoolkit/locales/es.yaml +163 -0
  10. iatoolkit/repositories/database_manager.py +3 -3
  11. iatoolkit/repositories/document_repo.py +1 -1
  12. iatoolkit/repositories/models.py +3 -4
  13. iatoolkit/repositories/profile_repo.py +0 -4
  14. iatoolkit/services/auth_service.py +44 -32
  15. iatoolkit/services/branding_service.py +35 -27
  16. iatoolkit/services/configuration_service.py +140 -0
  17. iatoolkit/services/dispatcher_service.py +20 -18
  18. iatoolkit/services/document_service.py +5 -2
  19. iatoolkit/services/excel_service.py +15 -11
  20. iatoolkit/services/file_processor_service.py +4 -12
  21. iatoolkit/services/history_service.py +8 -7
  22. iatoolkit/services/i18n_service.py +104 -0
  23. iatoolkit/services/jwt_service.py +7 -9
  24. iatoolkit/services/language_service.py +79 -0
  25. iatoolkit/services/load_documents_service.py +4 -4
  26. iatoolkit/services/mail_service.py +9 -4
  27. iatoolkit/services/onboarding_service.py +10 -4
  28. iatoolkit/services/profile_service.py +59 -38
  29. iatoolkit/services/prompt_manager_service.py +20 -16
  30. iatoolkit/services/query_service.py +15 -14
  31. iatoolkit/services/sql_service.py +6 -2
  32. iatoolkit/services/user_feedback_service.py +70 -29
  33. iatoolkit/static/js/chat_feedback_button.js +80 -0
  34. iatoolkit/static/js/chat_help_content.js +124 -0
  35. iatoolkit/static/js/chat_history_button.js +110 -0
  36. iatoolkit/static/js/chat_logout_button.js +36 -0
  37. iatoolkit/static/js/chat_main.js +32 -184
  38. iatoolkit/static/js/{chat_onboarding.js → chat_onboarding_button.js} +0 -1
  39. iatoolkit/static/js/chat_prompt_manager.js +94 -0
  40. iatoolkit/static/js/chat_reload_button.js +35 -0
  41. iatoolkit/static/styles/chat_iatoolkit.css +251 -205
  42. iatoolkit/static/styles/chat_modal.css +63 -95
  43. iatoolkit/static/styles/chat_public.css +107 -0
  44. iatoolkit/static/styles/landing_page.css +121 -167
  45. iatoolkit/templates/_company_header.html +20 -0
  46. iatoolkit/templates/_login_widget.html +10 -10
  47. iatoolkit/templates/base.html +36 -19
  48. iatoolkit/templates/change_password.html +24 -22
  49. iatoolkit/templates/chat.html +121 -93
  50. iatoolkit/templates/chat_modals.html +113 -74
  51. iatoolkit/templates/error.html +44 -8
  52. iatoolkit/templates/forgot_password.html +17 -15
  53. iatoolkit/templates/index.html +66 -81
  54. iatoolkit/templates/login_simulation.html +16 -5
  55. iatoolkit/templates/onboarding_shell.html +1 -2
  56. iatoolkit/templates/signup.html +22 -20
  57. iatoolkit/views/base_login_view.py +12 -1
  58. iatoolkit/views/change_password_view.py +50 -33
  59. iatoolkit/views/external_login_view.py +5 -11
  60. iatoolkit/views/file_store_api_view.py +7 -9
  61. iatoolkit/views/forgot_password_view.py +21 -19
  62. iatoolkit/views/help_content_api_view.py +54 -0
  63. iatoolkit/views/history_api_view.py +16 -12
  64. iatoolkit/views/home_view.py +61 -0
  65. iatoolkit/views/index_view.py +5 -34
  66. iatoolkit/views/init_context_api_view.py +16 -13
  67. iatoolkit/views/llmquery_api_view.py +38 -28
  68. iatoolkit/views/login_simulation_view.py +14 -2
  69. iatoolkit/views/login_view.py +48 -33
  70. iatoolkit/views/logout_api_view.py +49 -0
  71. iatoolkit/views/profile_api_view.py +46 -0
  72. iatoolkit/views/prompt_api_view.py +8 -8
  73. iatoolkit/views/signup_view.py +27 -25
  74. iatoolkit/views/{tasks_view.py → tasks_api_view.py} +10 -36
  75. iatoolkit/views/tasks_review_api_view.py +55 -0
  76. iatoolkit/views/user_feedback_api_view.py +21 -32
  77. iatoolkit/views/verify_user_view.py +33 -26
  78. {iatoolkit-0.59.1.dist-info → iatoolkit-0.67.0.dist-info}/METADATA +40 -22
  79. iatoolkit-0.67.0.dist-info/RECORD +120 -0
  80. iatoolkit-0.67.0.dist-info/licenses/LICENSE +21 -0
  81. iatoolkit/static/js/chat_context_reload.js +0 -54
  82. iatoolkit/static/js/chat_feedback.js +0 -115
  83. iatoolkit/static/js/chat_history.js +0 -127
  84. iatoolkit/static/styles/chat_info.css +0 -53
  85. iatoolkit/templates/_branding_styles.html +0 -53
  86. iatoolkit/templates/_navbar.html +0 -9
  87. iatoolkit/templates/header.html +0 -31
  88. iatoolkit/templates/test.html +0 -9
  89. iatoolkit/views/chat_token_request_view.py +0 -98
  90. iatoolkit/views/tasks_review_view.py +0 -83
  91. iatoolkit-0.59.1.dist-info/RECORD +0 -111
  92. {iatoolkit-0.59.1.dist-info → iatoolkit-0.67.0.dist-info}/WHEEL +0 -0
  93. {iatoolkit-0.59.1.dist-info → iatoolkit-0.67.0.dist-info}/top_level.txt +0 -0
@@ -3,62 +3,103 @@
3
3
  #
4
4
  # IAToolkit is open source software.
5
5
 
6
- from iatoolkit.repositories.models import UserFeedback
6
+ from iatoolkit.repositories.models import UserFeedback, Company
7
7
  from injector import inject
8
8
  from iatoolkit.repositories.profile_repo import ProfileRepo
9
+ from iatoolkit.services.i18n_service import I18nService
9
10
  from iatoolkit.infra.google_chat_app import GoogleChatApp
11
+ from iatoolkit.infra.mail_app import MailApp
10
12
  import logging
11
13
 
12
14
 
13
15
  class UserFeedbackService:
14
16
  @inject
15
- def __init__(self, profile_repo: ProfileRepo, google_chat_app: GoogleChatApp):
17
+ def __init__(self,
18
+ profile_repo: ProfileRepo,
19
+ i18n_service: I18nService,
20
+ google_chat_app: GoogleChatApp,
21
+ mail_app: MailApp):
16
22
  self.profile_repo = profile_repo
23
+ self.i18n_service = i18n_service
17
24
  self.google_chat_app = google_chat_app
25
+ self.mail_app = mail_app
26
+
27
+ def _send_google_chat_notification(self, space_name: str, message_text: str):
28
+ """Envía una notificación de feedback a un espacio de Google Chat."""
29
+ try:
30
+ chat_data = {
31
+ "type": "MESSAGE_TRIGGER",
32
+ "space": {"name": space_name},
33
+ "message": {"text": message_text}
34
+ }
35
+ chat_result = self.google_chat_app.send_message(message_data=chat_data)
36
+ if not chat_result.get('success'):
37
+ logging.warning(f"error sending notification to Google Chat: {chat_result.get('message')}")
38
+ except Exception as e:
39
+ logging.exception(f"error sending notification to Google Chat: {e}")
40
+
41
+ def _send_email_notification(self, destination_email: str, company_name: str, message_text: str):
42
+ """Envía una notificación de feedback por correo electrónico."""
43
+ try:
44
+ subject = f"Nuevo Feedback de {company_name}"
45
+ # Convertir el texto plano a un HTML simple para mantener los saltos de línea
46
+ html_body = message_text.replace('\n', '<br>')
47
+ self.mail_app.send_email(to=destination_email, subject=subject, body=html_body)
48
+ except Exception as e:
49
+ logging.exception(f"error sending email de feedback: {e}")
50
+
51
+ def _handle_notification(self, company: Company, message_text: str):
52
+ """Lee la configuración de la empresa y envía la notificación al canal correspondiente."""
53
+ feedback_params = company.parameters.get('user_feedback')
54
+ if not isinstance(feedback_params, dict):
55
+ logging.warning(f"missing 'user_feedback' configuration for company: {company.short_name}.")
56
+ return
57
+
58
+ # get channel and destination
59
+ channel = feedback_params.get('channel')
60
+ destination = feedback_params.get('destination')
61
+ if not channel or not destination:
62
+ logging.warning(f"invalid 'user_feedback' configuration for: {company.short_name}. Faltan 'channel' o 'destination'.")
63
+ return
64
+
65
+ if channel == 'google_chat':
66
+ self._send_google_chat_notification(space_name=destination, message_text=message_text)
67
+ elif channel == 'email':
68
+ self._send_email_notification(destination_email=destination, company_name=company.short_name, message_text=message_text)
69
+ else:
70
+ logging.warning(f"unknown feedback channel: '{channel}' for company {company.short_name}.")
18
71
 
19
72
  def new_feedback(self,
20
73
  company_short_name: str,
21
74
  message: str,
22
75
  user_identifier: str,
23
- space: str = None,
24
- type: str = None,
25
76
  rating: int = None) -> dict:
26
77
  try:
27
- # validate company
28
78
  company = self.profile_repo.get_company_by_short_name(company_short_name)
29
79
  if not company:
30
- return {'error': f'No existe la empresa: {company_short_name}'}
80
+ return {'error': self.i18n_service.t('errors.company_not_found', company_short_name=company_short_name)}
31
81
 
32
- # send notification to Google Chat
33
- chat_message = f"*Nuevo feedback de {company_short_name}*:\n*Usuario:* {user_identifier}\n*Mensaje:* {message}\n*Calificación:* {rating}"
34
-
35
- # TO DO: get the space and type from the input data
36
- chat_data = {
37
- "type": type,
38
- "space": {
39
- "name": space
40
- },
41
- "message": {
42
- "text": chat_message
43
- }
44
- }
45
-
46
- chat_result = self.google_chat_app.send_message(message_data=chat_data)
47
- if not chat_result.get('success'):
48
- logging.warning(f"Error al enviar notificación a Google Chat: {chat_result.get('message')}")
82
+ # 2. send notification using company configuration
83
+ notification_text = (f"*Nuevo feedback de {company_short_name}*:\n"
84
+ f"*Usuario:* {user_identifier}\n"
85
+ f"*Mensaje:* {message}\n"
86
+ f"*Calificación:* {rating if rating is not None else 'N/A'}")
87
+ self._handle_notification(company, notification_text)
49
88
 
50
- # create the UserFeedback object
51
- new_feedback = UserFeedback(
89
+ # 3. always save the feedback in the database
90
+ new_feedback_obj = UserFeedback(
52
91
  company_id=company.id,
53
92
  message=message,
54
93
  user_identifier=user_identifier,
55
94
  rating=rating
56
95
  )
57
- new_feedback = self.profile_repo.save_feedback(new_feedback)
58
- if not new_feedback:
59
- return {'error': 'No se pudo guardar el feedback'}
96
+ saved_feedback = self.profile_repo.save_feedback(new_feedback_obj)
97
+ if not saved_feedback:
98
+ logging.error(f"can't save feedback for user {user_identifier}/{company_short_name}")
99
+ return {'error': 'can not save the feedback'}
60
100
 
61
- return {'message': 'Feedback guardado correctamente'}
101
+ return {'success': True, 'message': 'Feedback guardado correctamente'}
62
102
 
63
103
  except Exception as e:
104
+ logging.exception(f"Error crítico en el servicio de feedback: {e}")
64
105
  return {'error': str(e)}
@@ -0,0 +1,80 @@
1
+ $(document).ready(function () {
2
+ const feedbackModal = $('#feedbackModal');
3
+ $('#submit-feedback').on('click', function () {
4
+ sendFeedback(this);
5
+ });
6
+
7
+ // Evento para enviar el feedback
8
+ async function sendFeedback(submitButton) {
9
+ toastr.options = {"positionClass": "toast-bottom-right", "preventDuplicates": true};
10
+ const feedbackText = $('#feedback-text').val().trim();
11
+ const activeStars = $('.star.active').length;
12
+
13
+ if (!feedbackText) {
14
+ toastr.error(t_js('feedback_comment_error'));
15
+ return;
16
+ }
17
+
18
+ if (activeStars === 0) {
19
+ toastr.error(t_js('feedback_rating_error'));
20
+ return;
21
+ }
22
+
23
+ submitButton.disabled = true;
24
+
25
+ // call the IAToolkit API to send feedback
26
+ const data = {
27
+ "user_identifier": window.user_identifier,
28
+ "message": feedbackText,
29
+ "rating": activeStars,
30
+ };
31
+
32
+ const responseData = await callToolkit('/api/feedback', data, "POST");
33
+ if (responseData)
34
+ toastr.success(t_js('feedback_sent_success_body'), t_js('feedback_sent_success_title'));
35
+ else
36
+ toastr.error(t_js('feedback_sent_error'));
37
+
38
+ submitButton.disabled = false;
39
+ feedbackModal.modal('hide');
40
+ }
41
+
42
+ // Evento para abrir el modal de feedback
43
+ $('#send-feedback-button').on('click', function () {
44
+ $('#submit-feedback').prop('disabled', false);
45
+ $('.star').removeClass('active hover-active'); // Resetea estrellas
46
+ $('#feedback-text').val('');
47
+ feedbackModal.modal('show');
48
+ });
49
+
50
+ // Evento que se dispara DESPUÉS de que el modal se ha ocultado
51
+ $('#feedbackModal').on('hidden.bs.modal', function () {
52
+ $('#feedback-text').val('');
53
+ $('.star').removeClass('active');
54
+ });
55
+
56
+ // Function for the star rating system
57
+ window.gfg = function (rating) {
58
+ $('.star').removeClass('active');
59
+ $('.star').each(function (index) {
60
+ if (index < rating) {
61
+ $(this).addClass('active');
62
+ }
63
+ });
64
+ };
65
+
66
+ $('.star').hover(
67
+ function () {
68
+ const rating = $(this).data('rating');
69
+ $('.star').removeClass('hover-active');
70
+ $('.star').each(function (index) {
71
+ if ($(this).data('rating') <= rating) {
72
+ $(this).addClass('hover-active');
73
+ }
74
+ });
75
+ },
76
+ function () {
77
+ $('.star').removeClass('hover-active');
78
+ });
79
+
80
+ });
@@ -0,0 +1,124 @@
1
+ $(document).ready(function () {
2
+
3
+ let helpContent = null; // Variable para cachear el contenido de ayuda
4
+
5
+ // Evento de clic en el botón de ayuda
6
+ $('#open-help-button').on('click', async function () {
7
+ const helpModal = new bootstrap.Modal(document.getElementById('helpModal'));
8
+ const accordionContainer = $('#help-accordion-container');
9
+ const spinner = $('#help-spinner');
10
+
11
+ // Si el contenido no se ha cargado, hacer la llamada a la API
12
+ if (helpContent) {
13
+ // Si el contenido ya está cacheado, solo muestra el modal
14
+ helpModal.show();
15
+ return;
16
+ }
17
+
18
+ spinner.show();
19
+ accordionContainer.hide();
20
+ helpModal.show();
21
+
22
+ try {
23
+ const helpContent = await callToolkit('/api/help-content', {}, "POST");
24
+
25
+ if (!helpContent) {
26
+ toastr.error('No se pudo cargar la guía de uso. Por favor, intente más tarde.');
27
+ spinner.hide();
28
+ helpModal.hide();
29
+ return;
30
+ }
31
+
32
+ // Construir el HTML del acordeón y mostrarlo
33
+ buildHelpAccordion(helpContent);
34
+ spinner.hide();
35
+ accordionContainer.show();
36
+
37
+ } catch (error) {
38
+ console.error("Error al cargar el contenido de ayuda:", error);
39
+ toastr.error('Ocurrió un error de red al cargar la guía.');
40
+ spinner.hide();
41
+ helpModal.hide();
42
+ }
43
+ });
44
+
45
+ /**
46
+ * Construye dinámicamente el HTML para el acordeón de ayuda a partir de los datos.
47
+ * @param {object} data El objeto JSON con el contenido de ayuda.
48
+ */
49
+ function buildHelpAccordion(data) {
50
+ const container = $('#help-accordion-container');
51
+ container.empty(); // Limpiar cualquier contenido previo
52
+
53
+ let accordionHtml = '';
54
+
55
+ if (data.example_questions) {
56
+ let contentHtml = '';
57
+ data.example_questions.forEach(cat => {
58
+ contentHtml += `<h6 class="fw-bold">${cat.category}</h6><ul>`;
59
+ cat.questions.forEach(q => contentHtml += `<li>${q}</li>`);
60
+ contentHtml += `</ul>`;
61
+ });
62
+ accordionHtml += createAccordionItem('examples', 'Preguntas de Ejemplo', contentHtml, true);
63
+ }
64
+
65
+ if (data.data_sources) {
66
+ let contentHtml = '<dl>';
67
+ data.data_sources.forEach(p => {
68
+ contentHtml += `<dt>${p.source}</dt><dd>${p.description}</dd>`;
69
+ });
70
+ contentHtml += `</dl>`;
71
+ accordionHtml += createAccordionItem('sources', 'Datos disponibles', contentHtml );
72
+ }
73
+
74
+ if (data.best_practices) {
75
+ let contentHtml = '<dl>';
76
+ data.best_practices.forEach(p => {
77
+ contentHtml += `<dt>${p.title}</dt><dd>${p.description}`;
78
+ if (p.example) {
79
+ contentHtml += `<br><small class="text-muted"><em>Ej: "${p.example}"</em></small>`;
80
+ }
81
+ contentHtml += `</dd>`;
82
+ });
83
+ contentHtml += `</dl>`;
84
+ accordionHtml += createAccordionItem('practices', 'Mejores Prácticas', contentHtml);
85
+ }
86
+
87
+ if (data.capabilities) {
88
+ let contentHtml = `<div class="row">`;
89
+ contentHtml += `<div class="col-md-6"><h6 class="fw-bold">Puede hacer:</h6><ul>${data.capabilities.can_do.map(item => `<li>${item}</li>`).join('')}</ul></div>`;
90
+ contentHtml += `<div class="col-md-6"><h6 class="fw-bold">No puede hacer:</h6><ul>${data.capabilities.cannot_do.map(item => `<li>${item}</li>`).join('')}</ul></div>`;
91
+ contentHtml += `</div>`;
92
+ accordionHtml += createAccordionItem('capabilities', 'Capacidades y Límites', contentHtml);
93
+ }
94
+
95
+ container.html(accordionHtml);
96
+ }
97
+
98
+ /**
99
+ * Helper para crear un item del acordeón de Bootstrap.
100
+ * @param {string} id El ID base para los elementos.
101
+ * @param {string} title El título que se muestra en el botón del acordeón.
102
+ * @param {string} contentHtml El HTML que va dentro del cuerpo colapsable.
103
+ * @param {boolean} isOpen Si el item debe estar abierto por defecto.
104
+ * @returns {string} El string HTML del item del acordeón.
105
+ */
106
+ function createAccordionItem(id, title, contentHtml, isOpen = false) {
107
+ const showClass = isOpen ? 'show' : '';
108
+ const collapsedClass = isOpen ? '' : 'collapsed';
109
+
110
+ return `
111
+ <div class="accordion-item">
112
+ <h2 class="accordion-header" id="heading-${id}">
113
+ <button class="accordion-button ${collapsedClass}" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-${id}" aria-expanded="${isOpen}" aria-controls="collapse-${id}">
114
+ ${title}
115
+ </button>
116
+ </h2>
117
+ <div id="collapse-${id}" class="accordion-collapse collapse ${showClass}" aria-labelledby="heading-${id}" data-bs-parent="#help-accordion-container">
118
+ <div class="accordion-body">
119
+ ${contentHtml}
120
+ </div>
121
+ </div>
122
+ </div>`;
123
+ }
124
+ });
@@ -0,0 +1,110 @@
1
+ $(document).ready(function () {
2
+ // Evento para abrir el modal de historial
3
+ const historyModal = $('#historyModal');
4
+
5
+ $('#history-button').on('click', function() {
6
+ historyModal.modal('show');
7
+ loadHistory();
8
+ });
9
+
10
+
11
+ // Función para cargar el historial
12
+ async function loadHistory() {
13
+ const historyLoading = $('#history-loading');
14
+ const historyContent = $('#history-content');
15
+ const historyTable = historyContent.find('table');
16
+ const noHistoryMessage = $('#no-history-message');
17
+
18
+ // prepare UI for loading
19
+ historyLoading.show();
20
+ historyContent.hide();
21
+ historyTable.show();
22
+ noHistoryMessage.hide();
23
+
24
+ // cal the toolkit, handle the response and errors
25
+ const data = await callToolkit("/api/history", {}, "POST");
26
+
27
+ if (!data || !data.history) {
28
+ historyLoading.hide();
29
+ toastr.error(t_js('error_loading_history'));
30
+ return;
31
+ }
32
+
33
+ if (data.history.length === 0) {
34
+ historyTable.hide();
35
+ noHistoryMessage.show();
36
+ } else
37
+ displayAllHistory(data.history);
38
+
39
+ historyLoading.hide();
40
+ historyContent.show();
41
+ }
42
+
43
+ // Función para mostrar todo el historial
44
+ function displayAllHistory(historyData) {
45
+ const historyTableBody = $('#history-table-body');
46
+
47
+ historyTableBody.empty();
48
+
49
+ // Filtrar solo consultas que son strings simples
50
+ const filteredHistory = historyData.filter(item => {
51
+ try {
52
+ JSON.parse(item.query);
53
+ return false;
54
+ } catch (e) {
55
+ return true;
56
+ }
57
+ });
58
+
59
+ // Poblar la tabla
60
+ filteredHistory.forEach((item, index) => {
61
+ const icon = $('<i>').addClass('bi bi-pencil-fill');
62
+
63
+ const edit_link = $('<a>')
64
+ .attr('href', 'javascript:void(0);')
65
+ .addClass('edit-pencil')
66
+ .attr('title', t_js('edit_query'))
67
+ .data('query', item.query)
68
+ .append(icon);
69
+
70
+ const row = $('<tr>').append(
71
+ $('<td>').addClass('text-nowrap').text(formatDate(item.created_at)),
72
+ $('<td>').text(item.query),
73
+ $('<td>').append(edit_link),
74
+ );
75
+ historyTableBody.append(row);
76
+ });
77
+ }
78
+
79
+ function formatDate(dateString) {
80
+ const date = new Date(dateString);
81
+ const padTo2Digits = (num) => num.toString().padStart(2, '0');
82
+
83
+ const day = padTo2Digits(date.getDate());
84
+ const month = padTo2Digits(date.getMonth() + 1);
85
+ const year = date.getFullYear();
86
+ const hours = padTo2Digits(date.getHours());
87
+ const minutes = padTo2Digits(date.getMinutes());
88
+
89
+ return `${day}-${month} ${hours}:${minutes}`;
90
+ }
91
+
92
+ // event handler for the edit pencil icon
93
+ $('#history-table-body').on('click', '.edit-pencil', function() {
94
+ const queryText = $(this).data('query');
95
+
96
+ // copy the text to the chat input box
97
+ if (queryText) {
98
+ $('#question').val(queryText);
99
+ autoResizeTextarea($('#question')[0]);
100
+ $('#send-button').removeClass('disabled');
101
+
102
+ // Cerrar el modal
103
+ $('#historyModal').modal('hide');
104
+
105
+ // Hacer focus en el textarea
106
+ if (window.innerWidth > 768)
107
+ $('#question').focus();
108
+ }
109
+ });
110
+ });
@@ -0,0 +1,36 @@
1
+ document.addEventListener('DOMContentLoaded', function() {
2
+ const logoutButton = document.getElementById('logout-button');
3
+ if (!logoutButton) {
4
+ console.warn('El botón de logout con id "logout-button" no fue encontrado.');
5
+ return;
6
+ }
7
+
8
+ if (window.toastr) {
9
+ toastr.options = { "positionClass": "toast-bottom-right", "preventDuplicates": true };
10
+ }
11
+
12
+ logoutButton.addEventListener('click', async function(event) {
13
+ event.preventDefault();
14
+
15
+ try {
16
+ const apiPath = '/api/logout';
17
+ const data = await callToolkit(apiPath, null, 'GET');
18
+
19
+ // Procesar la respuesta
20
+ if (data && data.status === 'success' && data.url) {
21
+ window.top.location.href = data.url;
22
+ } else {
23
+ // Si algo falla, callToolkit usualmente muestra un error.
24
+ // Mostramos un toast como fallback.
25
+ if (window.toastr) {
26
+ toastr.error('No se pudo procesar el cierre de sesión. Por favor, intente de nuevo.');
27
+ }
28
+ }
29
+ } catch (error) {
30
+ console.error('Error durante el logout:', error);
31
+ if (window.toastr) {
32
+ toastr.error('Ocurrió un error de red al intentar cerrar sesión.');
33
+ }
34
+ }
35
+ });
36
+ });