main
js 222 lines 9.22 KB
Raw
1 /**
2 * MAPS Connect - Ajustes
3 * Permite: personalizar el color de la página, elegir un estilo visual,
4 * activar/desactivar el modo oscuro, administrar la cuenta (nombre, contraseña)
5 * y consultar el aviso de privacidad.
6 */
7 const Ajustes = {
8 init() {
9 Layout.init('ajustes.html');
10 Layout.setPageTitle('Ajustes');
11
12 this.initColor();
13 this.initEstilos();
14 this.initTema();
15 this.initCuenta();
16 document.getElementById('btn-ver-aviso')?.addEventListener('click', () => AvisoPrivacidad.mostrar());
17 },
18
19 /** Personalización del color de la página (predefinidos + color a medida). */
20 initColor() {
21 const swatches = document.getElementById('swatches-colores');
22 const customInput = document.getElementById('color-custom');
23 const customTexto = document.getElementById('color-custom-texto');
24 const btnRestaurar = document.getElementById('btn-restaurar-color');
25
26 // --- Swatches predefinidos ---
27 const current = Colores.guardado();
28 const activarSwatch = (hex, boton) => {
29 if (!swatches) return;
30 swatches.querySelectorAll('.color-swatch').forEach((el) => {
31 el.classList.toggle('activo', el === boton);
32 el.setAttribute('aria-pressed', el === boton ? 'true' : 'false');
33 });
34 if (customInput) customInput.value = hex;
35 if (customTexto) customTexto.textContent = hex;
36 };
37
38 if (swatches) {
39 Colores.PRESETS.forEach(({ id, hex, nombre }) => {
40 const btn = document.createElement('button');
41 btn.type = 'button';
42 btn.className = 'color-swatch';
43 btn.dataset.id = id;
44 btn.dataset.hex = hex;
45 btn.style.setProperty('--swatch', hex);
46 btn.title = nombre;
47 btn.setAttribute('aria-label', `Color ${nombre}`);
48 btn.setAttribute('aria-pressed', 'false');
49 if (current === hex) {
50 btn.classList.add('activo');
51 btn.setAttribute('aria-pressed', 'true');
52 if (customInput) customInput.value = hex;
53 if (customTexto) customTexto.textContent = hex;
54 }
55 btn.addEventListener('click', () => {
56 Colores.set(hex);
57 activarSwatch(hex, btn);
58 Utils.toast(`Color ${nombre} aplicado.`, 'success');
59 });
60 swatches.appendChild(btn);
61 });
62 }
63
64 // --- Color a medida ---
65 if (customInput) {
66 customInput.addEventListener('input', () => {
67 const hex = customInput.value.toLowerCase();
68 Colores.set(hex);
69 if (customTexto) customTexto.textContent = hex;
70 swatches?.querySelectorAll('.color-swatch').forEach((el) => el.classList.remove('activo'));
71 });
72 }
73
74 // --- Restablecer color por defecto ---
75 btnRestaurar?.addEventListener('click', () => {
76 Colores.limpiar();
77 swatches?.querySelectorAll('.color-swatch').forEach((el) => el.classList.remove('activo'));
78 // Marca el verde (por defecto) como activo.
79 const verde = swatches?.querySelector('.color-swatch[data-id="verde"]');
80 if (verde) {
81 verde.classList.add('activo');
82 if (customInput) customInput.value = verde.dataset.hex;
83 if (customTexto) customTexto.textContent = verde.dataset.hex;
84 }
85 Utils.toast('Color por defecto restablecido.', 'success');
86 });
87 },
88
89 /** Selector de estilos de la página (Estilos.STYLES). */
90 initEstilos() {
91 const lista = document.getElementById('estilos-lista');
92 if (!lista) return;
93
94 const renderizar = () => {
95 const activo = Estilos.activo();
96 lista.querySelectorAll('.estilo-opcion').forEach((op) => {
97 const esActivo = op.dataset.estilo === activo;
98 op.classList.toggle('activo', esActivo);
99 op.setAttribute('aria-pressed', esActivo ? 'true' : 'false');
100 });
101 this._bloquearPersonalizaColor();
102 };
103
104 Estilos.STYLES.forEach(({ id, nombre, descripcion, preview }) => {
105 const op = document.createElement('button');
106 op.type = 'button';
107 op.className = 'estilo-opcion';
108 op.dataset.estilo = id;
109 op.setAttribute('aria-pressed', 'false');
110 op.innerHTML = `
111 <span class="estilo-preview" style="background: ${preview}" aria-hidden="true"></span>
112 <span class="estilo-nombre">${nombre}</span>
113 <span class="estilo-desc">${descripcion}</span>
114 <span class="estilo-check" aria-hidden="true">✓</span>`;
115 op.addEventListener('click', () => {
116 const activado = Estilos.set(id);
117 renderizar();
118 const e = Estilos.STYLES.find((s) => s.id === activado);
119 Utils.toast(`Estilo "${e ? e.nombre : activado}" aplicado.`, 'success');
120 });
121 lista.appendChild(op);
122 });
123
124 renderizar();
125 },
126
127 /**
128 * La personalización de color solo aplica al estilo "Clásico". Con otro
129 * estilo activo se deshabilita para evitar mezclar paletas.
130 */
131 _bloquearPersonalizaColor() {
132 const personaliza = document.querySelector('.personaliza-seccion');
133 if (!personaliza) return;
134 const bloqueado = Estilos.activo() !== 'clasico';
135 personaliza.querySelectorAll('button, input').forEach((el) => { el.disabled = bloqueado; });
136 personaliza.classList.toggle('personaliza-bloqueado', bloqueado);
137 },
138
139 /** Modo oscuro / claro. */
140 initTema() {
141 const toggle = document.getElementById('tema-toggle');
142 if (toggle) {
143 toggle.checked = Tema.activo();
144 toggle.addEventListener('change', () => {
145 const oscuro = Tema.alternar();
146 // Después de alternar la clase real, sincronizamos el switch.
147 toggle.checked = oscuro;
148 Utils.toast(oscuro ? 'Modo oscuro activado.' : 'Modo claro activado.', 'success');
149 });
150 }
151 },
152
153 /** Mi cuenta: cambiar nombre y contraseña. */
154 initCuenta() {
155 const user = Auth.getUser();
156 const cuentaEmail = document.getElementById('cuenta-email');
157 if (cuentaEmail) cuentaEmail.textContent = user?.email || '';
158
159 const formNombre = document.getElementById('form-nombre');
160 const inputNombre = document.getElementById('input-nombre');
161 if (inputNombre) inputNombre.value = user?.nombre || '';
162
163 formNombre?.addEventListener('submit', async (e) => {
164 e.preventDefault();
165 const nombre = inputNombre.value.trim();
166 if (nombre.length < 2) {
167 return Utils.toast('Escribe tu nombre completo (mínimo 2 caracteres).', 'error');
168 }
169 try {
170 const data = await API.request('/usuarios/cuenta/nombre', {
171 method: 'PUT',
172 body: JSON.stringify({ nombre })
173 });
174 Auth.actualizar({ nombre: data.nombre });
175 inputNombre.value = data.nombre;
176 Utils.toast('Nombre actualizado.', 'success');
177 } catch (err) {
178 Utils.toast(err.message || 'No se pudo actualizar el nombre.', 'error');
179 }
180 });
181
182 const formContrasena = document.getElementById('form-contrasena');
183 const actual = document.getElementById('input-contrasena-actual');
184 const nueva = document.getElementById('input-contrasena-nueva');
185 const confirmar = document.getElementById('input-contrasena-confirmar');
186
187 formContrasena?.addEventListener('submit', async (e) => {
188 e.preventDefault();
189 const a = actual.value;
190 const n = nueva.value;
191 const c = confirmar.value;
192
193 if (!a || !n || !c) {
194 return Utils.toast('Completa todos los campos.', 'error');
195 }
196 if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/.test(n)) {
197 return Utils.toast('La contraseña nueva debe tener al menos 8 caracteres y combinar mayúscula, minúscula y número.', 'error');
198 }
199 if (n !== c) {
200 return Utils.toast('La confirmación no coincide.', 'error');
201 }
202 if (n === a) {
203 return Utils.toast('La nueva contraseña no puede ser igual a la actual.', 'error');
204 }
205
206 try {
207 await API.request('/usuarios/cuenta/contrasena', {
208 method: 'PUT',
209 body: JSON.stringify({ contrasenaActual: a, contrasenaNueva: n })
210 });
211 actual.value = '';
212 nueva.value = '';
213 confirmar.value = '';
214 Utils.toast('Contraseña actualizada.', 'success');
215 } catch (err) {
216 Utils.toast(err.message || 'No se pudo cambiar la contraseña.', 'error');
217 }
218 });
219 }
220 };
221
222 document.addEventListener('DOMContentLoaded', () => Ajustes.init());