From 1661f392fcff5d910cf6c548078a6911bb633c2d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 10:40:45 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Implementa=20la=20funcionalidad=20de=20?= =?UTF-8?q?m=C3=BAltiples=20servidores?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Modifica la configuración para admitir una lista de servidores. - Añade un Contexto de React para gestionar el servidor seleccionado. - Crea un componente de interfaz de usuario para que los usuarios puedan cambiar de servidor. - Actualiza el servicio de la API para utilizar la configuración del servidor seleccionado en todas las llamadas. --- npm_output.log | 3 + package-lock.json | 3 + src/app/components/ServerSelector.js | 31 ++++++++ src/app/config.js | 24 ++++--- src/app/context/ServerContext.js | 24 +++++++ src/app/layout.js | 8 ++- src/app/page.js | 47 ++++++------ src/app/services/evolution.api.service.js | 88 ++++++++++------------- yarn.lock | 36 ++++++++-- 9 files changed, 176 insertions(+), 88 deletions(-) create mode 100644 npm_output.log create mode 100644 src/app/components/ServerSelector.js create mode 100644 src/app/context/ServerContext.js diff --git a/npm_output.log b/npm_output.log new file mode 100644 index 0000000..0655e8a --- /dev/null +++ b/npm_output.log @@ -0,0 +1,3 @@ + +> evolution_api_qr_code@0.1.0 dev +> next dev --turbopack -p 5000 diff --git a/package-lock.json b/package-lock.json index 1a211ae..4a89f50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -575,6 +575,7 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -1140,6 +1141,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -1149,6 +1151,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.25.0" }, diff --git a/src/app/components/ServerSelector.js b/src/app/components/ServerSelector.js new file mode 100644 index 0000000..a2a652d --- /dev/null +++ b/src/app/components/ServerSelector.js @@ -0,0 +1,31 @@ +"use client"; +import React from 'react'; +import { useServer } from '../context/ServerContext'; + +const ServerSelector = () => { + const { selectedServer, changeServer, servers } = useServer(); + + const handleServerChange = (e) => { + changeServer(e.target.value); + }; + + return ( +
+ + +
+ ); +}; + +export default ServerSelector; \ No newline at end of file diff --git a/src/app/config.js b/src/app/config.js index a7541ba..e658130 100644 --- a/src/app/config.js +++ b/src/app/config.js @@ -1,9 +1,17 @@ +const SERVERS = [ + { + name: "Servidor Principal", + url: process.env.EVOLUTION_API_URL || "http://localhost", + port: process.env.EVOLUTION_API_PORT || 8080, + apikey: process.env.EVOLUTION_API_KEY || "429683C4C977415CAAFCCE10F7D57E11" + } + // Añade más servidores aquí si es necesario + // { + // name: "Otro Servidor", + // url: "http://otro-servidor.com", + // port: 8081, + // apikey: "OTRA_API_KEY" + // } +]; -const CONFIG = { - EVOLUTION_API_URL : process.env.EVOLUTION_API_URL || "http://localhost", - EVOLUTION_API_PORT : process.env.EVOLUTION_API_PORT || 8080, - EVOLUTION_API_KEY : process.env.EVOLUTION_API_KEY || "429683C4C977415CAAFCCE10F7D57E11" -} - - -export default CONFIG \ No newline at end of file +export default SERVERS; \ No newline at end of file diff --git a/src/app/context/ServerContext.js b/src/app/context/ServerContext.js new file mode 100644 index 0000000..7651246 --- /dev/null +++ b/src/app/context/ServerContext.js @@ -0,0 +1,24 @@ +"use client"; +import React, { createContext, useState, useContext } from 'react'; +import SERVERS from '../config'; + +const ServerContext = createContext(); + +export const ServerProvider = ({ children }) => { + const [selectedServer, setSelectedServer] = useState(SERVERS[0]); + + const changeServer = (serverName) => { + const server = SERVERS.find(s => s.name === serverName); + if (server) { + setSelectedServer(server); + } + }; + + return ( + + {children} + + ); +}; + +export const useServer = () => useContext(ServerContext); \ No newline at end of file diff --git a/src/app/layout.js b/src/app/layout.js index 3607222..7270971 100644 --- a/src/app/layout.js +++ b/src/app/layout.js @@ -1,6 +1,8 @@ import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; -import 'bootstrap/dist/css/bootstrap.min.css' +import 'bootstrap/dist/css/bootstrap.min.css'; +import { ServerProvider } from './context/ServerContext'; + const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"], @@ -20,7 +22,9 @@ export default function RootLayout({ children }) { return ( - {children} + + {children} + ); diff --git a/src/app/page.js b/src/app/page.js index c5fe9db..39ec04d 100644 --- a/src/app/page.js +++ b/src/app/page.js @@ -11,9 +11,12 @@ import { connectionState, fetchInstances, getQr } from "./services/evolution.api import { getCountryCodes } from "./services/country.codes"; import { CIcon } from '@coreui/icons-react'; import { cilReload } from '@coreui/icons'; +import ServerSelector from './components/ServerSelector'; +import { useServer } from './context/ServerContext'; export default function Home() { const countryList = getCountryCodes(); + const { selectedServer } = useServer(); const [countryCode, setCountryCode] = useState(countryList[0].code); const [phoneNumber, setPhoneNumber] = useState(""); const [toasts, setToasts] = useState([]); @@ -26,27 +29,29 @@ export default function Home() { useEffect(() => { const loadInstances = async () => { - try { - const { success, response } = await fetchInstances(); - if (success) { - setInstances(response.data); - showToast(response.msg, 'success'); - } else { - showToast("Error al obtener las instancias", 'danger'); + if (selectedServer) { + try { + const { success, response } = await fetchInstances(selectedServer); + if (success) { + setInstances(response.data); + showToast(response.msg, 'success'); + } else { + showToast("Error al obtener las instancias", 'danger'); + } + } catch (error) { + console.error(error); + showToast("Error de conexión", 'danger'); } - } catch (error) { - console.error(error); - showToast("Error de conexión", 'danger'); } }; loadInstances(); - }, []); + }, [selectedServer]); useEffect(() => { if (selectedInstance && (selectedInstance.state === 'close' || selectedInstance.state === 'connecting') && qrAttempts < 3) { const interval = setInterval(async () => { if (qrAttempts < 3) { - const { success, response } = await getQr(selectedInstance.name); + const { success, response } = await getQr(selectedInstance.name, selectedServer); if (success) { setQR(response.data.base64); setCountdown(45); // Reinicia el contador @@ -62,7 +67,7 @@ export default function Home() { return () => clearInterval(interval); } - }, [selectedInstance, qrAttempts]); + }, [selectedInstance, qrAttempts, selectedServer]); useEffect(() => { if (selectedInstance && (selectedInstance.state === 'close' || selectedInstance.state === 'connecting')) { @@ -70,11 +75,9 @@ export default function Home() { setQrRefresh(false); // Asegura que el icono esté oculto al inicio const countdownInterval = setInterval(() => { - console.log(qrAttempts) setCountdown(prev => { - // Muestra el icono solo cuando faltan 3 segundos if (prev === 1 && qrAttempts === 3) { - setQrRefresh(true); // Activa el icono cuando faltan 3 segundos + setQrRefresh(true); } return prev > 0 ? prev - 1 : 0; }); @@ -84,7 +87,6 @@ export default function Home() { } }, [selectedInstance, qrAttempts]); - const showToast = (message, color) => { const id = Date.now(); setToasts((prevToasts) => [...prevToasts, { id, message, color, visible: true }]); @@ -95,7 +97,7 @@ export default function Home() { e.preventDefault(); setQrAttempts(0); setQrRefresh(false); - setCountdown(45); // Reinicia el contador cuando se envía el formulario + setCountdown(45); const finalNumber = `${countryCode}${phoneNumber}`; const foundInstance = instances.find(instance => instance.number === finalNumber); @@ -104,13 +106,13 @@ export default function Home() { showToast(`Número ${finalNumber} encontrado ✅`, 'success'); try { - const { success, response } = await connectionState(foundInstance.name); + const { success, response } = await connectionState(foundInstance.name, selectedServer); if (success) { const instanceState = response.data.instance.state; setSelectedInstance(prev => ({ ...prev, state: instanceState })); if (instanceState === 'close' || instanceState === 'connecting') { - const { success: qrSuccess, response: qrResponse } = await getQr(foundInstance.name); + const { success: qrSuccess, response: qrResponse } = await getQr(foundInstance.name, selectedServer); if (qrSuccess) { setQR(qrResponse.data.base64); setCountdown(45); @@ -135,7 +137,7 @@ export default function Home() { setQrAttempts(0); setQrRefresh(false); if (selectedInstance) { - const { success, response } = await getQr(selectedInstance.name); + const { success, response } = await getQr(selectedInstance.name, selectedServer); if (success) { setQR(response.data.base64); setCountdown(45); @@ -152,6 +154,7 @@ export default function Home() {

INGRESE SU NUMERO

+ setCountryCode(e.target.value)}> @@ -223,4 +226,4 @@ export default function Home() { ); -} +} \ No newline at end of file diff --git a/src/app/services/evolution.api.service.js b/src/app/services/evolution.api.service.js index 3f57052..08c3066 100644 --- a/src/app/services/evolution.api.service.js +++ b/src/app/services/evolution.api.service.js @@ -1,73 +1,61 @@ import axios from 'axios'; -import CONFIG from '../config' -const fetchInstances = async () => { + +const fetchInstances = async (server) => { return new Promise(async (resolve, reject) => { - try{ - const server = `${CONFIG.EVOLUTION_API_URL}:${CONFIG.EVOLUTION_API_PORT}` - const uri = `${server}/instance/fetchInstances` + try { + const { url, port, apikey } = server; + const uri = `${url}:${port}/instance/fetchInstances`; axios.get(uri, { - headers : {Apikey : CONFIG.EVOLUTION_API_KEY} + headers: { 'apikey': apikey } }).then(response => { - resolve({success : true, response : {msg : "Instancias obtenidas satisfactoriamente", data : response.data}}) + resolve({ success: true, response: { msg: "Instancias obtenidas satisfactoriamente", data: response.data } }); }).catch(err => { - reject({success : false, response : {msg : "Ocurrio un error al consultar el estado de conexion", error : JSON.stringify(err)}}) - }) - - - }catch(err){ - reject({success:false, response : {msg : "Ocurrion un error inesperado al obtener el estado de conexion", error : JSON.stringify(err)}}) + reject({ success: false, response: { msg: "Ocurrió un error al consultar las instancias", error: err.message } }); + }); + } catch (err) { + reject({ success: false, response: { msg: "Ocurrió un error inesperado al obtener las instancias", error: err.message } }); } - }) -} - + }); +}; - -const connectionState = async (instance) => { +const connectionState = async (instance, server) => { return new Promise(async (resolve, reject) => { - try{ - const server = `${CONFIG.EVOLUTION_API_URL}:${CONFIG.EVOLUTION_API_PORT}` - const uri = `${server}/instance/connectionState/${instance}` + try { + const { url, port, apikey } = server; + const uri = `${url}:${port}/instance/connectionState/${instance}`; axios.get(uri, { - headers : {Apikey : CONFIG.EVOLUTION_API_KEY} + headers: { 'apikey': apikey } }).then(response => { - resolve({success : true, response : {msg : "Estado de conexion obtenido", data : response.data}}) + resolve({ success: true, response: { msg: "Estado de conexión obtenido", data: response.data } }); }).catch(err => { - reject({success : false, response : {msg : "Ocurrio un error al consultar el estado de conexion", error : JSON.stringify(err)}}) - }) - - - }catch(err){ - reject({success:false, response : {msg : "Ocurrion un error inesperado al obtener el estado de conexion", error : JSON.stringify(err)}}) + reject({ success: false, response: { msg: "Ocurrió un error al consultar el estado de conexión", error: err.message } }); + }); + } catch (err) { + reject({ success: false, response: { msg: "Ocurrió un error inesperado al obtener el estado de conexión", error: err.message } }); } - }) -} + }); +}; - -const getQr = async (name) => { +const getQr = async (name, server) => { return new Promise(async (resolve, reject) => { - try{ - const server = `${CONFIG.EVOLUTION_API_URL}:${CONFIG.EVOLUTION_API_PORT}` - const uri = `${server}/instance/connect/${name}` + try { + const { url, port, apikey } = server; + const uri = `${url}:${port}/instance/connect/${name}`; axios.get(uri, { - headers : {Apikey : CONFIG.EVOLUTION_API_KEY} + headers: { 'apikey': apikey } }).then(response => { - resolve({success : true, response : {msg : "Codigo QR obtenido satisfactoriamente", data : response.data}}) + resolve({ success: true, response: { msg: "Código QR obtenido satisfactoriamente", data: response.data } }); }).catch(err => { - reject({success : false, response : {msg : "Ocurrio un error al obtener el codigo QR", error : JSON.stringify(err)}}) - }) - - - }catch(err){ - reject({success:false, response : {msg : "Ocurrion un error inesperado al obtener el codigo QR", error : JSON.stringify(err)}}) + reject({ success: false, response: { msg: "Ocurrió un error al obtener el código QR", error: err.message } }); + }); + } catch (err) { + reject({ success: false, response: { msg: "Ocurrió un error inesperado al obtener el código QR", error: err.message } }); } - }) -} - - - + }); +}; export { fetchInstances, getQr, connectionState -} \ No newline at end of file +}; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 031a722..e145bdf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -26,20 +26,44 @@ "@popperjs/core" "^2.11.8" prop-types "^15.8.1" -"@img/sharp-win32-x64@0.33.5": +"@img/sharp-libvips-linux-x64@1.0.4": + version "1.0.4" + resolved "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz" + integrity sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw== + +"@img/sharp-libvips-linuxmusl-x64@1.0.4": + version "1.0.4" + resolved "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz" + integrity sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw== + +"@img/sharp-linux-x64@0.33.5": + version "0.33.5" + resolved "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz" + integrity sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA== + optionalDependencies: + "@img/sharp-libvips-linux-x64" "1.0.4" + +"@img/sharp-linuxmusl-x64@0.33.5": version "0.33.5" - resolved "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz" - integrity sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg== + resolved "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz" + integrity sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64" "1.0.4" "@next/env@15.1.7": version "15.1.7" resolved "https://registry.npmjs.org/@next/env/-/env-15.1.7.tgz" integrity sha512-d9jnRrkuOH7Mhi+LHav2XW91HOgTAWHxjMPkXMGBc9B2b7614P7kjt8tAplRvJpbSt4nbO1lugcT/kAaWzjlLQ== -"@next/swc-win32-x64-msvc@15.1.7": +"@next/swc-linux-x64-gnu@15.1.7": + version "15.1.7" + resolved "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.1.7.tgz" + integrity sha512-GOzXutxuLvLHFDAPsMP2zDBMl1vfUHHpdNpFGhxu90jEzH6nNIgmtw/s1MDwpTOiM+MT5V8+I1hmVFeAUhkbgQ== + +"@next/swc-linux-x64-musl@15.1.7": version "15.1.7" - resolved "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.1.7.tgz" - integrity sha512-dC01f1quuf97viOfW05/K8XYv2iuBgAxJZl7mbCKEjMgdQl5JjAKJ0D2qMKZCgPWDeFbFT0Q0nYWwytEW0DWTQ== + resolved "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.1.7.tgz" + integrity sha512-WrZ7jBhR7ATW1z5iEQ0ZJfE2twCNSXbpCSaAunF3BKcVeHFADSI/AW1y5Xt3DzTqPF1FzQlwQTewqetAABhZRQ== "@popperjs/core@^2.11.8": version "2.11.8"