iatoolkit 0.18.1__py3-none-any.whl → 0.20.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.

@@ -13,15 +13,14 @@ import os
13
13
  def logout(company_short_name: str):
14
14
  SessionManager.clear()
15
15
  flash("Has cerrado sesión correctamente", "info")
16
- if company_short_name:
17
- return redirect(url_for('login', company_short_name=company_short_name))
18
- else:
19
- return redirect(url_for('home'))
16
+ return redirect(url_for('index', company_short_name=company_short_name))
20
17
 
21
18
 
22
19
  # this function register all the views
23
20
  def register_views(injector, app):
24
21
 
22
+ from iatoolkit.views.index_view import IndexView
23
+ from iatoolkit.views.init_context_view import InitContextView
25
24
  from iatoolkit.views.llmquery_view import LLMQueryView
26
25
  from iatoolkit.views.tasks_view import TaskView
27
26
  from iatoolkit.views.tasks_review_view import TaskReviewView
@@ -38,11 +37,14 @@ def register_views(injector, app):
38
37
  from iatoolkit.views.chat_token_request_view import ChatTokenRequestView
39
38
  from iatoolkit.views.download_file_view import DownloadFileView
40
39
 
41
- app.add_url_rule('/', view_func=HomeView.as_view('home'))
40
+ # landing page
41
+ app.add_url_rule('/<company_short_name>', view_func=IndexView.as_view('index'))
42
+ app.add_url_rule('/<company_short_name>/initiate_external_chat',
43
+ view_func=InitContextView.as_view('initiate_external_chat'))
42
44
 
43
45
  # login for external portals
44
- app.add_url_rule('/<company_short_name>/initiate_external_chat',
45
- view_func=InitiateExternalChatView.as_view('initiate_external_chat'))
46
+ app.add_url_rule('/<company_short_name>/<external_user_id>/init-context',
47
+ view_func=InitContextView.as_view('init-context'))
46
48
  app.add_url_rule('/<company_short_name>/external_login',
47
49
  view_func=ExternalChatLoginView.as_view('external_login'))
48
50
  app.add_url_rule('/auth/chat_token',
@@ -68,6 +70,9 @@ def register_views(injector, app):
68
70
  app.add_url_rule('/tasks/review/<int:task_id>', view_func=TaskReviewView.as_view('tasks-review'))
69
71
  app.add_url_rule('/load', view_func=FileStoreView.as_view('load'))
70
72
 
73
+ # login testing /login_testing
74
+ app.add_url_rule('/login_testing', view_func=HomeView.as_view('home'))
75
+
71
76
  app.add_url_rule(
72
77
  '/about', # URL de la ruta
73
78
  view_func=lambda: render_template('about.html'))
@@ -99,3 +104,9 @@ def register_views(injector, app):
99
104
  except FileNotFoundError:
100
105
  abort(404)
101
106
 
107
+ # Redirección opcional: hacer que la raíz '/' vaya a la landing de sample_company
108
+ @app.route('/')
109
+ def root_redirect():
110
+ return redirect(url_for('index', company_short_name='sample_company'))
111
+
112
+
iatoolkit/iatoolkit.py CHANGED
@@ -153,7 +153,6 @@ class IAToolkit:
153
153
  except PackageNotFoundError:
154
154
  pass
155
155
 
156
- self.app.wsgi_app = ProxyFix(self.app.wsgi_app, x_proto=1)
157
156
 
158
157
  self.app.config.update({
159
158
  'VERSION': self.version,
@@ -171,6 +170,9 @@ class IAToolkit:
171
170
  if parsed_url.scheme == 'https':
172
171
  self.app.config['PREFERRED_URL_SCHEME'] = 'https'
173
172
 
173
+ # 2. ProxyFix para no tener problemas con iframes y rutas
174
+ self.app.wsgi_app = ProxyFix(self.app.wsgi_app, x_proto=1)
175
+
174
176
  # Configuración para tokenizers en desarrollo
175
177
  if is_dev:
176
178
  os.environ["TOKENIZERS_PARALLELISM"] = "false"
@@ -379,6 +381,7 @@ class IAToolkit:
379
381
  'app_name': 'IAToolkit',
380
382
  'user': SessionManager.get('user'),
381
383
  'user_company': SessionManager.get('company_short_name'),
384
+ 'iatoolkit_base_url': os.environ.get('IATOOLKIT_BASE_URL', ''),
382
385
  }
383
386
 
384
387
  def _get_default_static_folder(self) -> str:
@@ -71,6 +71,14 @@ class BrandingService:
71
71
  if company and company.branding:
72
72
  final_branding_values.update(company.branding)
73
73
 
74
+ # Función para convertir HEX a RGB
75
+ def hex_to_rgb(hex_color):
76
+ hex_color = hex_color.lstrip('#')
77
+ return tuple(int(hex_color[i:i + 2], 16) for i in (0, 2, 4))
78
+
79
+ primary_rgb = hex_to_rgb(final_branding_values['brand_primary_color'])
80
+ secondary_rgb = hex_to_rgb(final_branding_values['brand_secondary_color'])
81
+
74
82
  # --- CONSTRUCCIÓN DE ESTILOS Y VARIABLES CSS ---
75
83
  header_style = (
76
84
  f"background-color: {final_branding_values['header_background_color']}; "
@@ -95,6 +103,8 @@ class BrandingService:
95
103
  :root {{
96
104
  --brand-primary-color: {final_branding_values['brand_primary_color']};
97
105
  --brand-secondary-color: {final_branding_values['brand_secondary_color']};
106
+ --brand-primary-color-rgb: {', '.join(map(str, primary_rgb))};
107
+ --brand-secondary-color-rgb: {', '.join(map(str, secondary_rgb))};
98
108
  --brand-text-on-primary: {final_branding_values['brand_text_on_primary']};
99
109
  --brand-text-on-secondary: {final_branding_values['brand_text_on_secondary']};
100
110
  --brand-modal-header-bg: {final_branding_values['header_background_color']};
@@ -12,7 +12,7 @@ $(document).ready(function () {
12
12
  const queryText = $(this).data('query');
13
13
 
14
14
  // Copiar el texto al textarea del chat
15
- if (queryText) { // Buena práctica: Asegurarse de que el dato no es indefinido
15
+ if (queryText) {
16
16
  $('#question').val(queryText);
17
17
  autoResizeTextarea($('#question')[0]);
18
18
  $('#send-button').removeClass('disabled');
@@ -92,7 +92,7 @@ $(document).ready(function () {
92
92
  }
93
93
  });
94
94
 
95
- // Poblar tabla con un método más seguro
95
+ // Poblar la tabla
96
96
  filteredHistory.forEach((item, index) => {
97
97
  const icon = $('<i>').addClass('bi bi-pencil-fill');
98
98
 
@@ -100,23 +100,21 @@ $(document).ready(function () {
100
100
  .attr('href', 'javascript:void(0);')
101
101
  .addClass('copy-query-icon')
102
102
  .attr('title', 'Copiar consulta al chat')
103
- .data('query', item.query) // Usar .data() es más seguro que un atributo de string
103
+ .data('query', item.query)
104
104
  .append(icon);
105
105
 
106
106
  const row = $('<tr>').append(
107
107
  $('<td>').text(index + 1),
108
108
  $('<td>').addClass('date-cell').text(formatDate(item.created_at)),
109
- $('<td>').text(item.query), // Usar .text() para evitar inyección de HTML
109
+ $('<td>').text(item.query),
110
110
  $('<td>').addClass('text-center').append(link)
111
111
  );
112
112
 
113
113
  historyTableBody.append(row);
114
114
  });
115
-
116
- // El event handler ya no se adjunta aquí.
117
115
  }
118
116
 
119
- // Función para formatear fecha (sin cambios)
117
+ // Función para formatear fecha
120
118
  function formatDate(dateString) {
121
119
  const date = new Date(dateString);
122
120
 
@@ -403,7 +403,7 @@ const showSpinner = function () {
403
403
  <div class="spinner-border text-primary" role="status" style="width: 1.5rem; height: 1.5rem; margin-right: 15px;">
404
404
  <span class="${accessibilityClass}">Loading...</span>
405
405
  </div>
406
- <span style="font-weight: bold; font-size: 15px;">Loading...</span>
406
+ <span style="font-weight: bold; font-size: 15px;">Cargando...</span>
407
407
  </div>
408
408
  `);
409
409
  $('#chat-container').append(spinner).scrollTop($('#chat-container')[0].scrollHeight);
@@ -185,6 +185,11 @@
185
185
  box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
186
186
  }
187
187
 
188
+ /* Aplica el color secundario de la marca al icono de ver archivos */
189
+ #view-files-button i {
190
+ color: var(--brand-secondary-color, #6c757d);
191
+ }
192
+
188
193
  /* Estilo para mensajes sutiles del sistema (ej. abortado) */
189
194
  .system-message {
190
195
  color: var(--brand-secondary-color, #6c757d); /* Usa el color secundario o un gris por defecto */
@@ -0,0 +1,156 @@
1
+ /* static/styles/landing_page.css */
2
+
3
+ /* --- Configuración General --- */
4
+ body {
5
+ background-color: #ffffff; /* Cambiado a blanco para que el hero destaque */
6
+ }
7
+
8
+ /* --- 1. Barra de Navegación Superior --- */
9
+ .landing-navbar {
10
+ background-color: var(--brand-primary-color, #0d6efd);
11
+ padding: 1rem 1.5rem;
12
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
13
+ }
14
+
15
+ .landing-navbar .navbar-brand {
16
+ color: var(--brand-text-on-primary, #FFFFFF);
17
+ font-weight: 700;
18
+ font-size: 1.8rem; /* Aumentado el tamaño del texto */
19
+ }
20
+
21
+ /* --- 2. Sección Principal (Hero) --- */
22
+ .hero-section {
23
+ padding: 5rem 0; /* <-- ESTA LÍNEA RESTAURA EL ESPACIADO SUPERIOR E INFERIOR */
24
+ background-color: #f8f9fa; /* Fondo gris claro para la sección principal */
25
+ }
26
+
27
+ /* Estilos para el texto de la propuesta de valor */
28
+ .value-proposition {
29
+ display: flex;
30
+ flex-direction: column;
31
+ justify-content: center;
32
+ height: 100%;
33
+ /* Se ajusta el padding para la nueva posición a la izquierda */
34
+ padding-left: 2rem;
35
+ }
36
+
37
+ .value-proposition .hero-title {
38
+ font-size: 2.8rem;
39
+ font-weight: 700;
40
+ line-height: 1.2;
41
+ color: #212529;
42
+ }
43
+
44
+ .value-proposition .hero-accelerator {
45
+ color: var(--brand-primary-color, #0d6efd);
46
+ }
47
+
48
+ .value-proposition .hero-subtitle {
49
+ font-size: 1.1rem;
50
+ color: #6c757d;
51
+ margin-top: 1.5rem;
52
+ }
53
+
54
+ /* --- Estilo para el título del widget de login --- */
55
+ .hero-section .bg-light h4 {
56
+ color: var(--brand-primary-color, #0d6efd);
57
+ }
58
+
59
+ /* --- Estilo para el texto introductorio del widget --- */
60
+ .widget-intro-text {
61
+ font-style: italic;
62
+ font-size: 0.9rem;
63
+ line-height: 1.4;
64
+ }
65
+
66
+
67
+ /* --- 3. Sección de Características --- */
68
+ .features-section {
69
+ padding: 5rem 0;
70
+ background-color: #ffffff; /* Fondo blanco para esta sección */
71
+ }
72
+
73
+ /* (El resto del archivo CSS permanece sin cambios) */
74
+ .feature-item {
75
+ text-align: center;
76
+ padding: 2rem;
77
+ background-color: #f8f9fa; /* Fondo gris claro para cada tarjeta */
78
+ border: 1px solid #dee2e6;
79
+ border-radius: 0.5rem;
80
+ height: 100%; /* CLAVE: Asegura que todas las tarjetas en una fila tengan el mismo alto */
81
+ }
82
+
83
+ .feature-icon {
84
+ font-size: 3rem;
85
+ color: var(--brand-primary-color); /* Usar color primario para más impacto */
86
+ margin-bottom: 1.5rem;
87
+ display: inline-block;
88
+ }
89
+
90
+ .feature-item h3 {
91
+ font-size: 1.4rem;
92
+ font-weight: 600;
93
+ margin-bottom: 1rem;
94
+ }
95
+
96
+ .feature-item p {
97
+ color: #6c757d;
98
+ }
99
+
100
+ .opensource-box {
101
+ background-color: #343a40;
102
+ color: #f8f9fa;
103
+ padding: 2rem;
104
+ border-radius: 0.5rem;
105
+ height: 100%; /* CLAVE: Asegura que tenga el mismo alto */
106
+ display: flex;
107
+ flex-direction: column;
108
+ justify-content: space-between;
109
+ text-align: center;
110
+ }
111
+
112
+ .opensource-icon {
113
+ font-size: 3rem; /* Tamaño consistente con los otros iconos */
114
+ color: var(--brand-text-on-primary, #FFFFFF);
115
+ opacity: 0.9;
116
+ margin-bottom: 1.5rem;
117
+ }
118
+
119
+ .opensource-content h3 {
120
+ font-weight: 600;
121
+ font-size: 1.4rem; /* Tamaño consistente */
122
+ margin-bottom: 1rem;
123
+ }
124
+
125
+ .opensource-content p {
126
+ margin-bottom: 1.5rem;
127
+ color: #adb5bd;
128
+ }
129
+
130
+ .opensource-content .btn {
131
+ width: 100%;
132
+ }
133
+
134
+ .pypi-link {
135
+ margin-top: 1.5rem;
136
+ padding-top: 1.5rem;
137
+ border-top: 1px solid #495057;
138
+ font-size: 0.9rem;
139
+ color: #ced4da;
140
+ }
141
+
142
+ .pypi-link code {
143
+ background-color: #212529;
144
+ padding: 0.2rem 0.5rem;
145
+ border-radius: 4px;
146
+ color: #e83e8c;
147
+ }
148
+
149
+ /* --- 4. Footer --- */
150
+ .landing-footer {
151
+ background-color: #343a40;
152
+ color: #adb5bd;
153
+ padding: 2rem 0;
154
+ text-align: center;
155
+ font-size: 0.9rem;
156
+ }
@@ -0,0 +1,53 @@
1
+ <!-- templates/_branding_styles.html -->
2
+ <style>
3
+ {{ branding.css_variables | safe }}
4
+
5
+ /* Clases de botón reutilizables basadas en el branding */
6
+ .btn-branded-primary {
7
+ background-color: var(--brand-primary-color);
8
+ color: var(--brand-text-on-primary);
9
+ border-color: var(--brand-primary-color);
10
+ transition: all 0.2s ease-in-out;
11
+ }
12
+ .btn-branded-primary:hover {
13
+ color: var(--brand-text-on-primary);
14
+ box-shadow: inset 0 0 0 200px rgba(0, 0, 0, 0.15);
15
+ }
16
+ .btn-branded-secondary {
17
+ background-color: var(--brand-secondary-color);
18
+ color: var(--brand-text-on-secondary);
19
+ border-color: var(--brand-secondary-color);
20
+ transition: all 0.2s ease-in-out;
21
+ }
22
+ .btn-branded-secondary:hover {
23
+ color: var(--brand-text-on-secondary);
24
+ box-shadow: inset 0 0 0 200px rgba(0, 0, 0, 0.15);
25
+ }
26
+ .form-title {
27
+ color: var(--brand-primary-color);
28
+ }
29
+
30
+ /* --- ESTILOS CORREGIDOS Y MÁS ESPECÍFICOS PARA SWEETALERT2 --- */
31
+
32
+ /* Botón de Confirmar (OK) */
33
+ .swal2-confirm.custom-confirm-button {
34
+ background-color: var(--brand-primary-color) !important;
35
+ color: var(--brand-text-on-primary) !important;
36
+ border: 1px solid var(--brand-primary-color) !important;
37
+ box-shadow: none !important;
38
+ }
39
+ .swal2-confirm.custom-confirm-button:hover {
40
+ box-shadow: inset 0 0 0 200px rgba(0, 0, 0, 0.15) !important;
41
+ }
42
+
43
+ /* Botón de Cancelar */
44
+ .swal2-cancel.custom-cancel-button {
45
+ background-color: var(--brand-secondary-color) !important;
46
+ color: var(--brand-text-on-secondary) !important;
47
+ border: 1px solid var(--brand-secondary-color) !important;
48
+ box-shadow: none !important;
49
+ }
50
+ .swal2-cancel.custom-cancel-button:hover {
51
+ box-shadow: inset 0 0 0 200px rgba(0, 0, 0, 0.15) !important;
52
+ }
53
+ </style>
@@ -0,0 +1,42 @@
1
+ <!-- templates/_login_widget.html -->
2
+ <div class="border rounded p-4 shadow-sm bg-light">
3
+ <!-- 1. Nuevo Encabezado de Marketing -->
4
+ <div class="text-center mb-4">
5
+ <h4 class="form-title fw-bold">Acceso a la Plataforma</h4>
6
+ <!-- Párrafo modificado con nuevo texto y nueva clase CSS -->
7
+ <p class="text-muted widget-intro-text">
8
+ Ingresa a la plataforma o regístrate y prueba una demo interactiva con nuestra empresa de ejemplo.
9
+ </p>
10
+ </div>
11
+
12
+ <!-- 2. Formulario de Inicio de Sesión -->
13
+ <form id="login-form"
14
+ action="{{ url_for('initiate_login', company_short_name=company_short_name) }}"
15
+ method="post">
16
+ <div class="mb-3">
17
+ <label for="email" class="form-label d-block text-muted">Correo Electrónico</label>
18
+ <input type="email" id="email" name="email" class="form-control" required>
19
+ </div>
20
+ <div class="mb-3">
21
+ <label for="password" class="form-label d-block text-muted">Contraseña</label>
22
+ <input type="password" id="password" name="password"
23
+ class="form-control" required>
24
+ </div>
25
+ <button type="submit" class="btn btn-branded-primary w-100 fw-bold py-2">
26
+ Ingresar
27
+ </button>
28
+ </form>
29
+
30
+ <!-- 3. Nueva Sección de Registro más Atractiva -->
31
+ <div class="mt-4 pt-3 text-center" style="border-top: 1px solid #e0e0e0;">
32
+ <span class="text-muted small">¿Eres nuevo aquí?</span>
33
+ <a href="{{ url_for('signup', company_short_name=company_short_name) }}" id="signup-link" class="fw-bold ms-1 text-decoration-none">Crea una cuenta gratis</a>
34
+ </div>
35
+
36
+ <!-- 4. Enlace de Recuperación de Contraseña (más sutil) -->
37
+ <div class="text-center mt-2">
38
+ <a href="{{ url_for('forgot_password', company_short_name=company_short_name) }}" class="text-decoration-none text-muted" style="font-size: 0.8rem;">
39
+ ¿Olvidaste tu contraseña?
40
+ </a>
41
+ </div>
42
+ </div>
@@ -0,0 +1,9 @@
1
+ <!-- templates/_navbar.html -->
2
+ <nav class="navbar landing-navbar">
3
+ <div class="container-fluid">
4
+ <!-- Convertido en un enlace a la landing page de la compañía actual -->
5
+ <a class="navbar-brand text-decoration-none" href="{{ url_for('index', company_short_name=company_short_name) }}">
6
+ IAToolkit
7
+ </a>
8
+ </div>
9
+ </nav>
@@ -14,10 +14,12 @@
14
14
  <link rel="stylesheet" href="{{ url_for('static', filename='styles/llm_output.css', _external=True) }}">
15
15
  </head>
16
16
  <body class="d-flex flex-column p-3" style="min-height: 100vh;">
17
+
17
18
  <main class="d-flex flex-column flex-grow-1">
18
19
  {% block content %}{% endblock %}
19
20
  </main>
20
21
 
22
+
21
23
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"></script>
22
24
  <script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
23
25
  <script src="https://cdn.jsdelivr.net/npm/filepond/dist/filepond.min.js"></script>
@@ -1,45 +1,64 @@
1
1
  {% extends "base.html" %}
2
2
 
3
- {% block title %}Cambiar Contraseña{% endblock %}
3
+ {% block title %}Cambiar Contraseña - {{ company.name }}{% endblock %}
4
4
 
5
5
  {% block content %}
6
- <div class="container vh-100 d-flex justify-content-center align-items-center">
7
- <div class="col-11 col-md-8 col-lg-5 border rounded p-3 shadow-sm">
8
- <h4 class="text-muted fw-semibold text-start mb-3">Cambiar Contraseña</h4>
9
- <h6 class="text-muted text-start mb-4">{{ email }}</h6>
10
-
11
- <form action="{{ url_for('change_password', company_short_name=company_short_name, token=token) }}" method="post">
12
- <div class="mb-3">
13
- <label for="temp_code" class="form-label text-muted">Código Temporal recibido</label>
14
- <input type="text" name="temp_code" id="temp_code"
15
- class="form-control text-muted" required
16
- autocomplete="off"
17
- value="{{ form_data.temp_code if form_data else '' }}">
18
- </div>
19
- <div class="mb-3">
20
- <label for="new_password" class="form-label text-muted">Nueva Contraseña</label>
21
- <input type="password" name="new_password" id="new_password"
22
- class="form-control text-muted" required
23
- value="{{ form_data.new_password if form_data else '' }}">
24
- <small class="form-text text-muted">
25
- La contraseña debe contener al menos 8 caracteres, una letra mayúscula, una letra minúscula, un número y un carácter especial.
26
- </small>
6
+ <!-- 1. Incluimos los estilos de branding reutilizables -->
7
+ {% include '_branding_styles.html' %}
27
8
 
28
- </div>
29
- <div class="mb-3">
30
- <label for="confirm_password" class="form-label text-muted">Confirmar Nueva Contraseña</label>
31
- <input type="password" name="confirm_password" id="confirm_password"
32
- class="form-control text-muted" required
33
- value="{{ form_data.password if form_data else '' }}">
34
- </div>
35
- <button type="submit" class="btn btn-primary w-100">Cambiar Contraseña</button>
9
+ <!-- Enlazamos la hoja de estilos de la landing page para reutilizar estilos -->
10
+ <link rel="stylesheet" href="{{ iatoolkit_base_url }}/static/styles/landing_page.css">
11
+
12
+ <!-- 2. Incluimos la barra de navegación reutilizable -->
13
+ {% include '_navbar.html' %}
14
+
15
+ <!-- 3. Sección contenedora para centrar el contenido -->
16
+ <section class="hero-section">
17
+ <div class="container">
18
+ <div class="row justify-content-center">
19
+ <div class="col-lg-6 col-md-8">
20
+ <div class="border rounded p-4 p-md-5 shadow-sm bg-light">
21
+ <h4 class="form-title fw-bold mb-3 text-center">Crear Nueva Contraseña</h4>
22
+
23
+ <p class="text-muted text-center mb-4">
24
+ Estás cambiando la contraseña para <strong>{{ email }}</strong>.
25
+ </p>
36
26
 
37
- <p class="text-muted text-start mt-3" style="text-align: justify;">
38
- Ingresa el código de seguridad que recibiste por correo y elige una nueva contraseña.
39
- Asegúrate de que sea segura y fácil de recordar.
40
- </p>
27
+ <form action="{{ url_for('change_password', company_short_name=company_short_name, token=token) }}" method="post">
41
28
 
42
- </form>
43
- </div>
44
- </div>
29
+ <!-- CAMPO RESTAURADO: Código Temporal -->
30
+ <div class="mb-3">
31
+ <label for="temp_code" class="form-label text-secondary">Código Temporal</label>
32
+ <input type="text" id="temp_code" name="temp_code" class="form-control"
33
+ required value="{{ form_data.temp_code if form_data else '' }}"
34
+ placeholder="Revisa tu correo electrónico">
35
+ </div>
36
+
37
+ <div class="mb-3">
38
+ <label for="new_password" class="form-label text-secondary">Nueva Contraseña</label>
39
+ <input type="password" id="new_password" name="new_password" class="form-control" required>
40
+ <div class="d-flex align-items-start text-muted mt-2" style="font-size: 0.8rem;">
41
+ <i class="bi bi-info-circle me-2" style="font-size: 0.9rem; line-height: 1.4;"></i>
42
+ <span>Debe contener al menos 8 caracteres, mayúscula, minúscula, número y un carácter especial.</span>
43
+ </div>
44
+ </div>
45
+
46
+ <div class="mb-3">
47
+ <label for="confirm_password" class="form-label text-secondary">Confirmar Nueva Contraseña</label>
48
+ <input type="password" id="confirm_password" name="confirm_password" class="form-control" required>
49
+ </div>
50
+
51
+ <button type="submit" class="btn btn-branded-primary w-100 fw-bold py-2 mt-3">Guardar Contraseña</button>
52
+ </form>
53
+
54
+ <div class="text-center mt-4 pt-3" style="border-top: 1px solid #e0e0e0;">
55
+ <a href="{{ url_for('index', company_short_name=company_short_name) }}" class="text-muted text-decoration-none fw-semibold">
56
+ <i class="bi bi-arrow-left me-1"></i>Volver al inicio
57
+ </a>
58
+ </div>
59
+ </div>
60
+ </div>
61
+ </div>
62
+ </div>
63
+ </section>
45
64
  {% endblock %}
@@ -118,8 +118,9 @@
118
118
  <div id="chat-input-bar" class="chat-input-bar d-flex align-items-center">
119
119
  <!-- Iconos de la izquierda -->
120
120
  <div class="d-flex align-items-center">
121
- <!-- BOTÓN PARA CONTROLAR EL COLLAPSE -->
122
- <a class="p-2" href="#prompt-assistant-collapse" data-bs-toggle="collapse" role="button" aria-expanded="false" aria-controls="prompt-assistant-collapse" title="Usar Asistente de Prompts">
121
+ <!-- varita magica -->
122
+ <a class="p-2" href="#prompt-assistant-collapse" data-bs-toggle="collapse" role="button"
123
+ aria-expanded="false" aria-controls="prompt-assistant-collapse" title="Usar Asistente de Prompts">
123
124
  <i class="bi bi-magic"></i>
124
125
  </a>
125
126
  <a class="p-2" href="javascript:void(0);" id="paperclip-button" title="Adjuntar archivos">
@@ -1,33 +1,48 @@
1
1
  {% extends "base.html" %}
2
2
 
3
- {% block title %}Recuperar Contraseña{% endblock %}
3
+ {% block title %}Recuperar Contraseña - {{ company.name }}{% endblock %}
4
4
 
5
5
  {% block content %}
6
- <div class="container d-flex justify-content-center align-items-center vh-100">
7
- <div class="col-11 col-md-6 col-lg-4 border rounded p-3 shadow-sm">
8
- <h4 class="text-muted fw-semibold text-start mb-3">Recuperar Contraseña</h4>
9
-
10
- <form action="{{ url_for('forgot_password', company_short_name=company_short_name) }}" method="post">
11
- <div class="mb-3">
12
- <label for="email" class="form-label text-muted">Correo Electrónico</label>
13
- <input type="email" id="email" name="email"
14
- class="form-control text-muted"
15
- required value="{{ form_data.email if form_data else '' }}"
16
- style="text-align: justify;">
17
- </div>
6
+ <!-- 1. Incluimos los estilos de branding reutilizables -->
7
+ {% include '_branding_styles.html' %}
8
+
9
+ <!-- Enlazamos la hoja de estilos de la landing page para reutilizar estilos -->
10
+ <link rel="stylesheet" href="{{ iatoolkit_base_url }}/static/styles/landing_page.css">
11
+
12
+ <!-- 2. Incluimos la barra de navegación reutilizable -->
13
+ {% include '_navbar.html' %}
14
+
15
+ <!-- 3. Sección contenedora para centrar el contenido -->
16
+ <section class="hero-section">
17
+ <div class="container">
18
+ <div class="row justify-content-center">
19
+ <div class="col-lg-6 col-md-8">
20
+ <div class="border rounded p-4 p-md-5 shadow-sm bg-light">
21
+ <h4 class="form-title fw-bold mb-3 text-center">Recuperar Contraseña</h4>
22
+
23
+ <p class="text-muted text-center mb-4">
24
+ Ingresa tu correo electrónico y te enviaremos un enlace para restablecer tu contraseña.
25
+ </p>
18
26
 
19
- <button type="submit" class="btn btn-primary w-100">Enviar Contraseña</button>
27
+ <form action="{{ url_for('forgot_password', company_short_name=company_short_name) }}" method="post">
28
+ <div class="mb-3">
29
+ <label for="email" class="form-label text-secondary">Correo Electrónico</label>
30
+ <input type="email" id="email" name="email"
31
+ class="form-control"
32
+ required value="{{ form_data.email if form_data else '' }}">
33
+ </div>
20
34
 
21
- <!-- Mensaje descriptivo -->
22
- <p class="text-muted text-start mt-3" style="text-align: justify;">
23
- Recibirás un correo con una nueva contraseña temporal junto con un código de seguridad.
24
- Usa este código para cambiar tu contraseña de forma segura y mantener tu cuenta protegida.
25
- </p>
35
+ <button type="submit" class="btn btn-branded-primary w-100 fw-bold py-2 mt-3">Enviar Enlace</button>
36
+ </form>
26
37
 
27
- <div class="text-center mt-3">
28
- <a href="{{ url_for('login', company_short_name=company_short_name) }}" class="text-muted text-decoration-none fw-semibold">Volver al Login</a>
38
+ <div class="text-center mt-4 pt-3" style="border-top: 1px solid #e0e0e0;">
39
+ <a href="{{ url_for('index', company_short_name=company_short_name) }}" class="text-muted text-decoration-none fw-semibold">
40
+ <i class="bi bi-arrow-left me-1"></i>Volver al inicio
41
+ </a>
42
+ </div>
43
+ </div>
44
+ </div>
29
45
  </div>
30
- </form>
31
- </div>
32
- </div>
46
+ </div>
47
+ </section>
33
48
  {% endblock %}