From d558142192c05324c69138ee7d76ec45bc499c38 Mon Sep 17 00:00:00 2001 From: Jesus Echenagucia Date: Fri, 28 Aug 2026 18:03:09 +0000 Subject: [PATCH 01/85] =?UTF-8?q?a=C3=B1adi=20las=20migraciones=20al=20git?= =?UTF-8?q?ignore=20que=20no=20estaban?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + package-lock.json | 155 ---------------------------------------------- 2 files changed, 1 insertion(+), 155 deletions(-) diff --git a/.gitignore b/.gitignore index 80704f4..12e3b32 100755 --- a/.gitignore +++ b/.gitignore @@ -80,3 +80,4 @@ database.database database.db diagram.png __pycache__/ +migrations \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8d43d98..398b3c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -877,19 +877,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", @@ -997,14 +984,6 @@ "@babel/types": "^7.20.7" } }, - "node_modules/@types/node": { - "version": "16.11.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", - "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/@types/prop-types": { "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", @@ -1307,14 +1286,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -3915,29 +3886,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/string.prototype.matchall": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", @@ -4074,35 +4022,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/terser": { - "version": "5.38.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.38.1.tgz", - "integrity": "sha512-GWANVlPM/ZfYzuPHjq0nxT+EbOEDDN3Jwhwdg1D8TU8oSkktp8w64Uq4auuGLxFSoNTRDncTq2hQHX1Ld9KHkA==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -4944,18 +4863,6 @@ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true }, - "@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, "@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", @@ -5044,14 +4951,6 @@ "@babel/types": "^7.20.7" } }, - "@types/node": { - "version": "16.11.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", - "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==", - "dev": true, - "optional": true, - "peer": true - }, "@types/prop-types": { "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", @@ -5252,14 +5151,6 @@ "update-browserslist-db": "^1.1.1" } }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "optional": true, - "peer": true - }, "call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -6982,28 +6873,6 @@ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "peer": true - } - } - }, "string.prototype.matchall": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", @@ -7094,30 +6963,6 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, - "terser": { - "version": "5.38.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.38.1.tgz", - "integrity": "sha512-GWANVlPM/ZfYzuPHjq0nxT+EbOEDDN3Jwhwdg1D8TU8oSkktp8w64Uq4auuGLxFSoNTRDncTq2hQHX1Ld9KHkA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "optional": true, - "peer": true - } - } - }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", From e700ebc86ab2531160d6c32e3bef8f66aa2bb990 Mon Sep 17 00:00:00 2001 From: Jesus Echenagucia Date: Wed, 2 Sep 2026 20:07:31 +0000 Subject: [PATCH 02/85] registro e inicio de sesion terminado --- src/front/components/AuthLayout.jsx | 58 ++++++++++++ src/front/pages/ForgotPassword.jsx | 88 ++++++++++++++++++ src/front/pages/Login.jsx | 113 ++++++++++++++++++++++ src/front/pages/Register.jsx | 139 ++++++++++++++++++++++++++++ src/front/pages/ResetPassword.jsx | 94 +++++++++++++++++++ src/front/services/authService.js | 93 +++++++++++++++++++ 6 files changed, 585 insertions(+) create mode 100644 src/front/components/AuthLayout.jsx create mode 100644 src/front/pages/ForgotPassword.jsx create mode 100644 src/front/pages/Login.jsx create mode 100644 src/front/pages/Register.jsx create mode 100644 src/front/pages/ResetPassword.jsx create mode 100644 src/front/services/authService.js diff --git a/src/front/components/AuthLayout.jsx b/src/front/components/AuthLayout.jsx new file mode 100644 index 0000000..385f018 --- /dev/null +++ b/src/front/components/AuthLayout.jsx @@ -0,0 +1,58 @@ +import React from "react"; + +export const AuthLayout = ({ children }) => { + return ( +
+
+
+
+ +
+
+ C +
+ ClientFlow +
+ +
+ + Sistema Operativo Inteligente + +

+ Todo tu negocio.
Un solo flujo. +

+

+ Desde la primera consulta hasta el trabajo terminado: clientes, equipo, operaciones, conocimiento y agentes de IA en una sola plataforma. +

+
+ +
+ © 2026 ClientFlow - 4Geeks Academy +
+
+ +
+
+ + + +
+ +
+ ES ▼ +
+
+
+ +
+ {children} +
+ +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/src/front/pages/ForgotPassword.jsx b/src/front/pages/ForgotPassword.jsx new file mode 100644 index 0000000..a228eb9 --- /dev/null +++ b/src/front/pages/ForgotPassword.jsx @@ -0,0 +1,88 @@ +import React, { useState } from "react"; +import { Link } from "react-router-dom"; +import { AuthLayout } from "../components/AuthLayout"; +import { authService } from "../services/authService"; + +export const ForgotPassword = () => { + const [email, setEmail] = useState(""); + const [submitted, setSubmitted] = useState(false); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + + const handleSubmit = async (e) => { + e.preventDefault(); + if (!email) { + setError("Por favor ingresa tu email."); + return; + } + setError(""); + setLoading(true); + + try { + await authService.forgotPassword(email); + setLoading(false); + setSubmitted(true); + } catch (err) { + setLoading(false); + setError(err.message || "Error al procesar la solicitud."); + } +}; + + return ( + +
+
+ 🔑 +
+ +

Recuperar contraseña

+

Te enviaremos las instrucciones para restablecer tu acceso.

+ + {error && ( +
+ {error} +
+ )} + + {submitted ? ( +
+

Correo enviado con éxito.

+

Revisa tu bandeja de entrada y sigue el enlace para recuperar tu contraseña.

+ Volver al inicio de sesión +
+ ) : ( +
+
+ +
+ ✉️ + setEmail(e.target.value)} + placeholder="tu@email.com" + className="form-control bg-light border-start-0 shadow-none py-2" + /> +
+
+ + +
+ )} + + {!submitted && ( +

+ ¿Recordaste tu contraseña? Iniciar sesión +

+ )} +
+
+ ); +}; \ No newline at end of file diff --git a/src/front/pages/Login.jsx b/src/front/pages/Login.jsx new file mode 100644 index 0000000..9628854 --- /dev/null +++ b/src/front/pages/Login.jsx @@ -0,0 +1,113 @@ +import React, { useState } from "react"; +import { Link, useNavigate } from "react-router-dom"; +import useGlobalReducer from "../hooks/useGlobalReducer"; +import { AuthLayout } from "../components/AuthLayout"; +import { authService } from "../services/authService"; // Importamos el servicio + +export const Login = () => { + const { dispatch } = useGlobalReducer(); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + const navigate = useNavigate(); + + const handleSubmit = async (e) => { + e.preventDefault(); + if (!email || !password) { + setError("Por favor completa todos los campos requeridos."); + return; + } + setError(""); + setLoading(true); + + try { + + const data = await authService.login(email, password); + + localStorage.setItem("jwt_token", data.token); + localStorage.setItem("user_data", JSON.stringify(data.user)); + + dispatch({ + type: "SET_AUTH", + payload: { token: data.token, user: data.user } + }); + + setLoading(false); + navigate("/"); + } catch (err) { + setLoading(false); + setError(err.message || "Ocurrió un error."); + } + }; + + return ( + +
+
+ 🔓 +
+ +

Bienvenido de nuevo

+

Accede a tu espacio de trabajo ClientFlow

+ + {error && ( +
+ {error} +
+ )} + +
+
+ +
+ ✉️ + setEmail(e.target.value)} + placeholder="tu@email.com" + className="form-control bg-light border-start-0 shadow-none py-2" + /> +
+
+ +
+
+ + ¿Olvidaste tu contraseña? +
+
+ 🔒 + setPassword(e.target.value)} + placeholder="••••••••" + className="form-control bg-light border-start-0 shadow-none py-2" + /> +
+
+ +
+ + +
+ + +
+ +

+ ¿Aún no tienes cuenta? Crear cuenta +

+
+
+ ); +}; \ No newline at end of file diff --git a/src/front/pages/Register.jsx b/src/front/pages/Register.jsx new file mode 100644 index 0000000..50c9b0a --- /dev/null +++ b/src/front/pages/Register.jsx @@ -0,0 +1,139 @@ +import React, { useState } from "react"; +import { Link, useNavigate } from "react-router-dom"; +import { AuthLayout } from "../components/AuthLayout"; +import { authService } from "../services/authService"; + +export const Register = () => { + const [formData, setFormData] = useState({ + firstName: "", + lastName: "", + email: "", + company: "", + code: "", + password: "", + }); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + const navigate = useNavigate(); + + const handleChange = (e) => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + +const handleSubmit = async (e) => { + e.preventDefault(); + if (!formData.firstName || !formData.email || !formData.password) { + setError("Por favor completa los campos obligatorios."); + return; + } + setError(""); + setLoading(true); + + try { + await authService.register(formData); + setLoading(false); + alert("¡Cuenta activada con éxito!"); + navigate("/login"); + } catch (err) { + setLoading(false); + setError(err.message || "No se pudo conectar con el servidor."); + } +}; + + return ( + +
+
+ 👤 +
+ +

Registrate

+ + + {error && ( +
+ {error} +
+ )} + +
+
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ + + +
+ + +
+ + +
+ +

+ ¿Ya tienes cuenta? Iniciar sesión +

+
+
+ ); +}; \ No newline at end of file diff --git a/src/front/pages/ResetPassword.jsx b/src/front/pages/ResetPassword.jsx new file mode 100644 index 0000000..0f5d8a1 --- /dev/null +++ b/src/front/pages/ResetPassword.jsx @@ -0,0 +1,94 @@ +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { AuthLayout } from "../components/AuthLayout"; +import { authService } from "../services/authService"; + +export const ResetPassword = () => { + const [password, setPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + const navigate = useNavigate(); + + const handleSubmit = async (e) => { + e.preventDefault(); + if (!password || !confirmPassword) { + setError("Por favor completa todos los campos."); + return; + } + if (password !== confirmPassword) { + setError("Las contraseñas no coinciden."); + return; + } + setError(""); + setLoading(true); + + try { + await authService.resetPassword(password); + setLoading(false); + alert("Contraseña actualizada con éxito"); + navigate("/login"); + } catch (err) { + setLoading(false); + setError(err.message || "Error al actualizar la contraseña."); + } +}; + + return ( + +
+
+ 🔒 +
+ +

Nueva contraseña

+

Introduce y confirma tu nueva contraseña de acceso.

+ + {error && ( +
+ {error} +
+ )} + +
+
+ +
+ 🔒 + setPassword(e.target.value)} + placeholder="••••••••" + className="form-control bg-light border-start-0 shadow-none py-2" + /> +
+
+ +
+ +
+ 🔒 + setConfirmPassword(e.target.value)} + placeholder="••••••••" + className="form-control bg-light border-start-0 shadow-none py-2" + /> +
+
+ + +
+
+
+ ); +}; \ No newline at end of file diff --git a/src/front/services/authService.js b/src/front/services/authService.js new file mode 100644 index 0000000..684aacb --- /dev/null +++ b/src/front/services/authService.js @@ -0,0 +1,93 @@ +// src/front/services/authService.js + +const USE_REAL_API = false; +const API_URL = import.meta.env.VITE_BACKEND_URL || "https://ideal-space-spoon-69xjqwgrx47pc5jgw-3001.app.github.dev/"; + +export const authService = { + login: async (email, password) => { + if (USE_REAL_API) { + const response = await fetch(`${API_URL}/api/login`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email, password }) + }); + const data = await response.json(); + if (!response.ok) throw new Error(data.message || "Error al iniciar sesión"); + return data; + } else { + return new Promise((resolve, reject) => { + setTimeout(() => { + if (email === "error@test.com") { + reject(new Error("Credenciales inválidas (Simulado)")); + } else { + resolve({ + token: "mock_jwt_token_abc123", + user: { email: email, name: "Usuario de Prueba" } + }); + } + }, 1000); + }); + } + }, + + register: async (formData) => { + if (USE_REAL_API) { + const response = await fetch(`${API_URL}/api/register`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(formData) + }); + const data = await response.json(); + if (!response.ok) throw new Error(data.message || "Error al registrar"); + return data; + } else { + return new Promise((resolve, reject) => { + setTimeout(() => { + if (!formData.code || formData.code.trim() === "") { + reject(new Error("El código de acceso es obligatorio (Simulado)")); + } else { + resolve({ message: "Usuario registrado con éxito (Simulado)" }); + } + }, 1000); + }); + } + }, + + forgotPassword: async (email) => { + if (USE_REAL_API) { + const response = await fetch(`${API_URL}/api/forgot-password`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email }) + }); + const data = await response.json(); + if (!response.ok) throw new Error(data.message || "Error al enviar correo"); + return data; + } else { + return new Promise((resolve) => { + setTimeout(() => { + resolve({ message: "Correo de recuperación enviado (Simulado)" }); + }, 1000); + }); + } + }, + + resetPassword: async (password) => { + if (USE_REAL_API) { + const response = await fetch(`${API_URL}/api/reset-password`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ password }) + }); + const data = await response.json(); + if (!response.ok) throw new Error(data.message || "Error al actualizar contraseña"); + return data; + } else { + return new Promise((resolve) => { + setTimeout(() => { + resolve({ message: "Contraseña actualizada con éxito (Simulado)" }); + }, 1000); + }); + } + } +}; \ No newline at end of file From 2e53a4ed883a0e5c74a6ba6f6ef308ce90fa00e1 Mon Sep 17 00:00:00 2001 From: Jesus Echenagucia Date: Wed, 2 Sep 2026 20:20:16 +0000 Subject: [PATCH 03/85] inicio de sesion y registro terminados --- src/front/pages/Layout.jsx | 4 ++-- src/front/routes.jsx | 44 +++++++++++++++--------------------- src/front/store.js | 46 ++++++++++++++++---------------------- 3 files changed, 39 insertions(+), 55 deletions(-) diff --git a/src/front/pages/Layout.jsx b/src/front/pages/Layout.jsx index 9bfa313..21e3974 100644 --- a/src/front/pages/Layout.jsx +++ b/src/front/pages/Layout.jsx @@ -7,9 +7,9 @@ import { Footer } from "../components/Footer" export const Layout = () => { return ( - + -