diff --git a/dev-dist/sw.js b/dev-dist/sw.js index 8f413b5..8018ddf 100644 --- a/dev-dist/sw.js +++ b/dev-dist/sw.js @@ -79,7 +79,7 @@ define(['./workbox-fde070c5'], (function (workbox) { 'use strict'; */ workbox.precacheAndRoute([{ "url": "/offline.html", - "revision": "0.pef915uabhg" + "revision": "0.f6jp5lmvjvg" }], {}); workbox.cleanupOutdatedCaches(); workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("/offline.html"), { diff --git a/package-lock.json b/package-lock.json index 9165d8e..cea14e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8593,7 +8593,7 @@ }, "node_modules/dayjs": { "version": "1.11.13", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/debug": { @@ -13269,6 +13269,13 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", + "license": "MIT", + "peer": true + }, "node_modules/js-tokens": { "version": "4.0.0", "license": "MIT" @@ -20600,6 +20607,21 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "dev": true, diff --git a/package.json b/package.json index 29e3b5c..75d4583 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,8 @@ "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", "test": "vitest", - "test:unit": "vitest", + "test:unit": "vitest", "cypress:run": "cypress run" - }, "dependencies": { "@emotion/react": "^11.11.3", diff --git a/src/views/Admin/reportes.jsx b/src/views/Admin/reportes.jsx index 76a7537..36072d5 100644 --- a/src/views/Admin/reportes.jsx +++ b/src/views/Admin/reportes.jsx @@ -1,30 +1,55 @@ import React, { useState, useEffect } from "react"; import { Pie } from "react-chartjs-2"; -import { Chart as ChartJS, Title, Tooltip, Legend, ArcElement, CategoryScale, LinearScale } from "chart.js"; - -ChartJS.register(Title, Tooltip, Legend, ArcElement, CategoryScale, LinearScale); +import { + Chart as ChartJS, + Title, + Tooltip, + Legend, + ArcElement, + CategoryScale, + LinearScale, +} from "chart.js"; + +ChartJS.register( + Title, + Tooltip, + Legend, + ArcElement, + CategoryScale, + LinearScale +); function ResultadosEncuestas() { - const [resultados, setResultados] = useState([]); - const [totalPersonas, setTotalPersonas] = useState(0); // Nuevo estado para personas únicas - const [cargando, setCargando] = useState(true); + const [resultados, setResultados] = useState([]); // Datos de las encuestas + const [totalPersonas, setTotalPersonas] = useState(0); // Total de personas que han respondido + const [cargando, setCargando] = useState(true); // Estado de carga + + const mapeoPreguntas = { + question1: "¿Qué tan fácil fue encontrar la información que buscabas?", + question2: "¿Cómo calificarías la facilidad de uso del sistema para agendar tu cita?", + question3: "¿Qué tan satisfecho estás con el proceso de agendar una cita?", + question4: "¿Qué tan rápido te pareció el sistema para agendar tu cita?", + question5: "¿Qué tan claro fue el mensaje de confirmación de tu cita?", + }; useEffect(() => { + // Obtener los resultados de las encuestas const obtenerResultados = async () => { try { - const response = await fetch("http://localhost:3000/resultados", { - method: "GET", - headers: { "Content-Type": "application/json" }, - }); + const response = await fetch( + "https://backopt-production.up.railway.app/feedback/obtenerResultadosEncuestas", + { + method: "GET", + headers: { "Content-Type": "application/json" }, + } + ); if (response.ok) { const data = await response.json(); - if (data) { - setResultados(data.data || []); // Manejo de resultados de encuestas - setTotalPersonas(data.totalPersonas || 0); // Guardar el total de personas únicas + setResultados(data.data || []); // Maneja los resultados de las encuestas + setTotalPersonas(data.totalPersonas || 0); // Guarda el total de personas únicas } else { - console.error("La respuesta no contiene datos válidos"); setResultados([]); setTotalPersonas(0); } @@ -32,14 +57,14 @@ function ResultadosEncuestas() { alert("Error al obtener los resultados"); } } catch (error) { - console.error("Error al obtener los resultados:", error); alert("Hubo un error al obtener los resultados"); + console.error("Error al obtener los resultados:", error); } finally { - setCargando(false); + setCargando(false); // Finaliza la carga } }; - obtenerResultados(); + obtenerResultados(); // Llamar la función de obtención de resultados }, []); if (cargando) { @@ -47,32 +72,33 @@ function ResultadosEncuestas() { } const procesarDatos = () => { - const respuestasPorPregunta = {}; - - resultados.forEach((encuesta) => { - const { pregunta, respuestas } = encuesta; - - if (!respuestasPorPregunta[pregunta]) { - respuestasPorPregunta[pregunta] = { "1": 0, "2": 0, "3": 0, "4": 0, "5": 0 }; - } - - Object.entries(respuestas).forEach(([calificacion, cantidad]) => { - respuestasPorPregunta[pregunta][calificacion] += cantidad; + if (typeof resultados !== "object" || Array.isArray(resultados)) { + console.error("Los resultados no son un objeto:", resultados); + return { labels: [], datasets: [] }; + } + + const respuestasPorPregunta = []; + + Object.entries(resultados).forEach(([pregunta, respuestas]) => { + const preguntaReal = mapeoPreguntas[pregunta] || pregunta; // Usa el mapeo o la clave por defecto + const respuestasArray = Object.values(respuestas); + + respuestasPorPregunta.push({ + label: preguntaReal, + data: respuestasArray, + backgroundColor: [ + "#FF6F61", + "#6B5B95", + "#88B04B", + "#F7CAC9", + "#92A8D1", + ], }); }); - const labels = Object.keys(respuestasPorPregunta); - const datasets = labels.map((pregunta) => { - return { - label: pregunta, - data: Object.values(respuestasPorPregunta[pregunta]), - backgroundColor: ["#FF6F61", "#6B5B95", "#88B04B", "#F7CAC9", "#92A8D1"], - }; - }); - return { - labels: ["1", "2", "3", "4", "5"], // Mantenemos las etiquetas originales (números) - datasets, + labels: ["1", "2", "3", "4", "5"], + datasets: respuestasPorPregunta, }; }; @@ -83,7 +109,7 @@ function ResultadosEncuestas() {

Resultados de Encuestas Web

- {/* Mostrar cantidad de personas únicas que han respondido */} + {/* Mostrar el total de personas que han respondido */}

Total de personas que han respondido: {totalPersonas}

@@ -93,7 +119,10 @@ function ResultadosEncuestas() { ) : (
{data.datasets.map((dataset, index) => ( -
+

{dataset.label}

{ - const calificacion = estrellas[tooltipItem.dataIndex]; // Convertir índice a estrellas + const calificacion = + estrellas[tooltipItem.dataIndex]; // Convertir índice a estrellas const value = dataset.data[tooltipItem.dataIndex]; return `${calificacion}: Personas que han respondido: ${value}`; }, diff --git a/src/views/feedback/encuesta.jsx b/src/views/feedback/encuesta.jsx index 2ee8801..e760391 100644 --- a/src/views/feedback/encuesta.jsx +++ b/src/views/feedback/encuesta.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { useNavigate } from "react-router-dom"; // Función para decodificar JWT @@ -24,6 +24,9 @@ function EncuestaCitas() { const [encuestaCompletada, setEncuestaCompletada] = useState(false); // Estado para verificar si ya se completó la encuesta const navigate = useNavigate(); + // Usamos un useRef para asegurarnos de que registrarAcceso se ejecute solo una vez + const accesoRegistrado = useRef(false); + const preguntas = [ "¿Qué tan fácil fue encontrar la información que buscabas?", "¿Cómo calificarías la facilidad de uso del sistema para agendar tu cita?", @@ -33,40 +36,53 @@ function EncuestaCitas() { ]; useEffect(() => { - // Recuperar el token del localStorage const token = localStorage.getItem("token"); if (token) { - const decodedToken = parseJwt(token); - const idUsuario = decodedToken.clienteId; // Asumimos que el idUsuario está en el campo `id` - - // Verificar si la encuesta ya fue completada - const verificarEncuesta = async () => { - try { - const response = await fetch(`http://localhost:3000/Encuesta/completada?idUsuario=${idUsuario}`, { - method: "GET", - headers: { "Content-Type": "application/json" }, - }); - - if (response.ok) { - const data = await response.json(); - if (data.completada) { - setEncuestaCompletada(true); // Establecer que la encuesta ya fue completada - } - } - } catch (error) { - console.error("Error al verificar encuesta:", error); - } finally { - setCargando(false); // Finaliza el estado de carga + const decodedToken = parseJwt(token); + console.log(decodedToken); // Verifica el contenido del token + + const idUsuario = decodedToken.clienteId; + + if (!idUsuario) { + console.error("El idUsuario no se pudo obtener del token."); + return; // Salir si no hay idUsuario } - }; - verificarEncuesta(); + // Evitar llamar registrarAcceso si ya fue completado + if (!accesoRegistrado.current && !encuestaCompletada) { + const registrarAcceso = async () => { + try { + const response = await fetch("https://backopt-production.up.railway.app/feedback/acceso", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ idUsuario }), + }); + + if (response.ok) { + const data = await response.json(); + if (data.estado === "Encuesta ya completada") { + setEncuestaCompletada(true); // Marcar como completada si ya está hecho + } + } else { + console.error("Error al registrar el acceso al feedback"); + } + } catch (error) { + console.error("Error al registrar acceso:", error); + } finally { + setCargando(false); // Finaliza el estado de carga + } + }; + + registrarAcceso(); // Solo ejecutar si la encuesta no está completada + accesoRegistrado.current = true; // Marcar como registrado + } else { + setCargando(false); // Si ya está completada, no hacer nada + } } else { - // Si no hay token, redirigir al login o manejar el error - navigate("/login"); + navigate("/login"); // Redirige al login si no hay token } - }, [navigate]); +}, [encuestaCompletada, navigate]); // Solo ejecutará cuando cambie `encuestaCompletada` const handleRespuestaChange = (index, value) => { setRespuestas((prev) => ({ @@ -87,14 +103,22 @@ function EncuestaCitas() { const decodedToken = parseJwt(token); const idUsuario = decodedToken.clienteId; + if (!idUsuario) { + alert("No se pudo obtener el idUsuario del token."); + return; // Salir si no hay idUsuario + } + try { - const response = await fetch("http://localhost:3000/Encuesta", { - method: "POST", + const response = await fetch("https://backopt-production.up.railway.app/feedback/completar", { + method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - idUsuario, // Usar el id del usuario decodificado del JWT - respuestas, - preguntas, // Envía las preguntas al backend + idUsuario, + question1: respuestas[0], + question2: respuestas[1], + question3: respuestas[2], + question4: respuestas[3], + question5: respuestas[4], }), }); diff --git a/src/views/inicio.jsx b/src/views/inicio.jsx index f55f556..b0c77e2 100644 --- a/src/views/inicio.jsx +++ b/src/views/inicio.jsx @@ -1,16 +1,41 @@ -import { useState } from "react"; -/* import Navbar from "../components/BarraNavegacion"; */ +import { useState, useEffect } from "react"; +import Barra from "../components/Navegacion/barra"; import Slider from "../home/slider"; import Fot from "../components/Footer"; +import Scrool from "../components/scroll"; import imagen from "../img/Venta.png"; import imagen2 from "../img/lentes2.png"; -import Scrool from '../components/scroll'; -import Barra from "../components/Navegacion/barra"; import lente from "../img/3d.jpg"; - - +import Encuesta from "../views/feedback/encuesta"; + +// Función para decodificar el JWT y obtener el clienteId +function getClienteIdFromToken() { + const token = localStorage.getItem("token"); + + if (token) { + const base64Url = token.split(".")[1]; + const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); + const jsonPayload = decodeURIComponent( + window + .atob(base64) + .split("") + .map(function (c) { + return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2); + }) + .join("") + ); + const decodedToken = JSON.parse(jsonPayload); + return decodedToken.clienteId; + } + + return null; +} function App() { + const [mostrarEncuesta, setMostrarEncuesta] = useState(false); // Estado para la encuesta pendiente + const [idUsuario, setIdUsuario] = useState(null); // Estado para idUsuario (inicializado como null) + const [encuesta, setEncuesta] = useState(null); // Estado para almacenar la información de la encuesta + const [mostrarElemento, setMostrarElemento] = useState(false); const [mostrarElemento2, setMostrarElemento2] = useState(false); const [mostrarElemento3, setMostrarElemento3] = useState(false); @@ -42,180 +67,105 @@ function App() { setIsZoomed3(false); }; + // Función para verificar si el usuario tiene una encuesta pendiente + const obtenerEncuestaPendiente = async (idUsuario) => { + try { + const response = await fetch("https://backopt-production.up.railway.app/feedback/pendiente", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ idUsuario }), + }); + + const data = await response.json(); + + if (data.estado === "Pendiente") { + return { + estado: "Pendiente", + id_encuesta: data.id_encuesta, + mensaje: data.mensaje, + }; + } else { + return { estado: "Completado", mensaje: data.mensaje }; + } + } catch (error) { + console.error("Error al obtener la encuesta pendiente:", error); + return { + estado: "Error", + mensaje: "No se pudo verificar la encuesta pendiente.", + }; + } + }; + + useEffect(() => { + const id = getClienteIdFromToken(); + if (id) { + setIdUsuario(id); + } else { + console.error("No se pudo obtener el idUsuario del token."); + } + }, []); + + useEffect(() => { + if (idUsuario) { + const verificarEncuesta = async () => { + const encuesta = await obtenerEncuestaPendiente(idUsuario); + if (encuesta.estado === "Pendiente") { + setEncuesta(encuesta); // Guardamos la encuesta pendiente + setMostrarEncuesta(true); // Mostramos la encuesta + } + }; + verificarEncuesta(); + } + }, [idUsuario]); + return ( <> -{/* */} + - - + {mostrarEncuesta && encuesta && ( +
+ +
+ )} + + {/* Contenido de la página */}
- +
+ +

- - -
-
- - - - +
+
Sunset in the mountains - - - - -
-
- The Coldest Sunset -
-
- Estos lentes ofrecen un estilo único y moderno que te hará - destacar en cualquier ocasión. Con su diseño elegante y - funcional, podrás disfrutar de la máxima protección UV y una - visión nítida y clara en todo momento. - - {mostrarElemento && ( -
- Su montura ligera y resistente garantiza una comodidad - duradera, mientras que sus cristales de alta calidad te - brindan una visión sin igual. ¡Prepárate para lucir - increíble con los lentes The Coldest Sunset! -
- )} -
-
-
- -
-
- - - - - {/* Aquí empieza El segundo */} -
- Sunset in the mountains -
-
- The Coldest Sunset -
-
- Estos lentes ofrecen un estilo único y moderno que te hará - destacar en cualquier ocasión. Con su diseño elegante y - funcional, podrás disfrutar de la máxima protección UV y una - visión nítida y clara en todo momento. - - {mostrarElemento2 && ( -
- Su montura ligera y resistente garantiza una comodidad - duradera, mientras que sus cristales de alta calidad te - brindan una visión sin igual. ¡Prepárate para lucir - increíble con los lentes The Coldest Sunset! -
- )} -
-
-
- -
- - {/* Aquí empieza el tercero */} -
+
Sunset in the mountains -
-
- The Coldest Sunset -
-
- Estos lentes ofrecen un estilo único y moderno que te hará - destacar en cualquier ocasión. Con su diseño elegante y - funcional, podrás disfrutar de la máxima protección UV y una - visión nítida y clara en todo momento. - - {mostrarElemento3 && ( -
- Su montura ligera y resistente garantiza una comodidad - duradera, mientras que sus cristales de alta calidad te - brindan una visión sin igual. ¡Prepárate para lucir - increíble con los lentes The Coldest Sunset! -
- )} -
-
-
- -
-

-
- -
+
+

+ ¡Estás a un paso de obtener lo que necesitas! +

+

+ Encuentra tus productos ópticos favoritos y realiza tu compra de + forma fácil y rápida. +

+
-
- - + );