diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd-production.yml
similarity index 91%
rename from .github/workflows/ci-cd.yml
rename to .github/workflows/ci-cd-production.yml
index ce44b67..a5c8eff 100644
--- a/.github/workflows/ci-cd.yml
+++ b/.github/workflows/ci-cd-production.yml
@@ -1,4 +1,4 @@
-name: CI/CD Pipeline ELysium
+name: CI/CD Pipeline ELysium Production
on:
push:
@@ -6,13 +6,12 @@ on:
- main
pull_request:
branches:
- - develop
- main
env:
- AZURE_WEBAPP_NAME: elysiumFrontEnd
+ AZURE_WEBAPP_NAME: Eros
AZURE_WEBAPP_PACKAGE_PATH: '.'
- NODE_VERSION: '18.x'
+ NODE_VERSION: '22.x'
jobs:
build-and-deploy:
diff --git a/.github/workflows/ci-cd-testing.yml b/.github/workflows/ci-cd-testing.yml
new file mode 100644
index 0000000..800b2b7
--- /dev/null
+++ b/.github/workflows/ci-cd-testing.yml
@@ -0,0 +1,57 @@
+name: CI/CD Pipeline ELysium Testing
+
+on:
+ push:
+ branches:
+ - develop
+ pull_request:
+ branches:
+ - develop
+
+env:
+ AZURE_WEBAPP_NAME: cicero
+ AZURE_WEBAPP_PACKAGE_PATH: '.'
+ NODE_VERSION: '22.x'
+
+jobs:
+ build-and-deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - uses: azure/login@v1
+ with:
+ creds: ${{ secrets.AZURE_TEST_ENVIRONMENT }}
+
+ - name: Cache Node.js modules
+ uses: actions/cache@v3
+ with:
+ path: ~/.npm
+ key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
+ restore-keys: |
+ ${{ runner.os }}-node-
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm install
+
+ - name: Run tests
+ run: npm run test --if-present
+
+ - name: Build project
+ run: npm run build --if-present
+
+ - name: Deploy to Azure Web Apps
+ uses: azure/webapps-deploy@v3
+ with:
+ app-name: ${{ env.AZURE_WEBAPP_NAME }}
+ package: ./build
+
+ - name: Logout from Azure
+ run: az logout
diff --git a/src/components/Admin/charts/RangoFechasChart.jsx b/src/components/Admin/charts/RangoFechasChart.jsx
index 4c55bef..e6c9522 100644
--- a/src/components/Admin/charts/RangoFechasChart.jsx
+++ b/src/components/Admin/charts/RangoFechasChart.jsx
@@ -9,8 +9,6 @@ const RangoFechasChart = ({ reservas }) => {
const svg = d3.select(container);
svg.selectAll("*").remove(); // Limpiar el SVG
- if (!reservas || reservas.length === 0) return;
-
// Ajustar tamaño dinámico según el contenedor
const containerWidth = container.clientWidth || 600;
const containerHeight = containerWidth * 0.6; // Mantener proporción
@@ -21,6 +19,17 @@ const RangoFechasChart = ({ reservas }) => {
const margin = { top: 20, right: 20, bottom: 30, left: 100 };
+ if (reservas.length === 0) {
+ svg.append("text")
+ .attr("x", containerWidth / 2)
+ .attr("y", containerHeight / 2)
+ .attr("text-anchor", "middle")
+ .attr("font-size", "16px")
+ .attr("fill", "#666")
+ .text("No hay datos disponibles");
+ return;
+ }
+
// Agrupar reservas por salón
const reservasPorSalon = d3.rollup(reservas, (v) => v.length, (d) => d.idSalon);
const data = Array.from(reservasPorSalon, ([salon, count]) => ({ salon, count }));
diff --git a/src/components/Table/UserRow.jsx b/src/components/Table/UserRow.jsx
index efe003b..73c111d 100644
--- a/src/components/Table/UserRow.jsx
+++ b/src/components/Table/UserRow.jsx
@@ -39,7 +39,7 @@ const RoleBadge = styled.span`
background-color: ${props => props.$isAdmin ? '#e3f2fd' : '#f5f5f5'};
color: ${props => props.$isAdmin ? '#1976d2' : '#616161'};
`;
-function UserRow({ user, onUpdateUser, onEditUser }) {
+function UserRow({ user, onEditUser }) {
// Extraemos las propiedades del usuario
const {idInstitucional, nombre, apellido, correoInstitucional, isAdmin, activo } = user;
diff --git a/src/components/Table/UserTable.jsx b/src/components/Table/UserTable.jsx
index 8c06c09..7c3bd13 100644
--- a/src/components/Table/UserTable.jsx
+++ b/src/components/Table/UserTable.jsx
@@ -56,7 +56,6 @@ function UserTable({ users, onUpdateUser }) {
))}
diff --git a/src/components/popup/CRUDSalonModal/AddSalonModal.jsx b/src/components/popup/CRUDSalonModal/AddSalonModal.jsx
index 9a1438f..f7f33b4 100644
--- a/src/components/popup/CRUDSalonModal/AddSalonModal.jsx
+++ b/src/components/popup/CRUDSalonModal/AddSalonModal.jsx
@@ -4,14 +4,24 @@ import CRUDSalonForm from "./CRUDSalonForm";
function AddSalonModal({ onClose, newSalon, setNewSalon, handleAddSalon }) {
const [tempSalon, setTempSalon] = useState({
+ mnemonico: newSalon?.mnemonico || "",
nombre: newSalon?.nombre || "",
descripcion: newSalon?.descripcion || "",
- mnemonico: newSalon?.mnemonico || "",
ubicacion: newSalon?.ubicacion || "",
capacidad: newSalon?.capacidad || 0,
recursos: newSalon?.recursos || [{ nombre: "", cantidad: 1, especificaciones: [], activo: true }],
});
+ const isFormComplete = () => {
+ return (
+ tempSalon.mnemonico.trim() !== "" &&
+ tempSalon.nombre.trim() !== "" &&
+ tempSalon.descripcion.trim() !== "" &&
+ tempSalon.ubicacion.trim() !== "" &&
+ tempSalon.capacidad > 0
+ );
+ };
+
const handleGuardar = () => {
setNewSalon({
...tempSalon,
@@ -32,7 +42,7 @@ function AddSalonModal({ onClose, newSalon, setNewSalon, handleAddSalon }) {
/>
-
+
diff --git a/src/components/popup/CRUDSalonModal/CRUDSalonModal.css b/src/components/popup/CRUDSalonModal/CRUDSalonModal.css
index 37f541c..5d40549 100644
--- a/src/components/popup/CRUDSalonModal/CRUDSalonModal.css
+++ b/src/components/popup/CRUDSalonModal/CRUDSalonModal.css
@@ -78,6 +78,14 @@
cursor: pointer;
}
+ .popup-overlay .salon-modal .save-button:disabled {
+ background: #ddd;
+ color: #999;
+ cursor: not-allowed;
+ opacity: 0.6;
+ }
+
+
.popup-overlay .modal-content .capacity-container {
display: flex;
flex-direction: column;
diff --git a/src/components/popup/CRUDSalonModal/EditarSalonModal.jsx b/src/components/popup/CRUDSalonModal/EditarSalonModal.jsx
index 86aebfe..4d839fc 100644
--- a/src/components/popup/CRUDSalonModal/EditarSalonModal.jsx
+++ b/src/components/popup/CRUDSalonModal/EditarSalonModal.jsx
@@ -3,25 +3,40 @@ import "./CRUDSalonModal.css";
import CRUDSalonForm from "./CRUDSalonForm";
function EditarSalonModal({ onClose, newSalon, setNewSalon, handleEdit }) {
- const defaultSalon = {
- nombre: "",
- descripcion: "",
- mnemonico: "",
- ubicacion: "",
- capacidad: 0,
- recursos: [],
+ const [isInitialized, setIsInitialized] = useState(false);
+ const [tempSalon, setTempSalon] = useState({
+ mnemonico: newSalon?.mnemonico || "",
+ nombre: newSalon?.nombre || "",
+ descripcion: newSalon?.descripcion || "",
+ ubicacion: newSalon?.ubicacion || "",
+ capacidad: newSalon?.capacidad || 0,
+ recursos: newSalon?.recursos || [{ nombre: "", cantidad: 1, especificaciones: [], activo: true }],
+ });
+
+ const isFormComplete = () => {
+ return (
+ tempSalon.mnemonico.trim() !== "" &&
+ tempSalon.nombre.trim() !== "" &&
+ tempSalon.descripcion.trim() !== "" &&
+ tempSalon.ubicacion.trim() !== "" &&
+ tempSalon.capacidad > 0
+ );
};
- const [tempSalon, setTempSalon] = useState(newSalon || defaultSalon);
useEffect(() => {
- if (newSalon) {
+ if (!isInitialized && newSalon) {
setTempSalon(newSalon);
+ setIsInitialized(true);
}
- }, [newSalon]);
+ }, [newSalon, isInitialized]);
const handleGuardar = () => {
- setNewSalon(tempSalon);
- handleEdit();
+ const updatedSalon = {
+ ...tempSalon,
+ recursos: tempSalon.recursos?.length > 0 ? tempSalon.recursos : [{ nombre: "", cantidad: 1, especificaciones: [], activo: true }],
+ };
+ setNewSalon(updatedSalon);
+ handleEdit(updatedSalon);
};
return (
@@ -36,7 +51,7 @@ function EditarSalonModal({ onClose, newSalon, setNewSalon, handleEdit }) {
/>
-
+
diff --git a/src/config/config.js b/src/config/config.js
index 57b0bf8..838ed60 100644
--- a/src/config/config.js
+++ b/src/config/config.js
@@ -1 +1 @@
-export const BASE_URL = "http://localhost:8080/api";
\ No newline at end of file
+export const BASE_URL = "https://hades-g4apbhdua4gtbbf5.canadacentral-01.azurewebsites.net/api";
\ No newline at end of file
diff --git a/src/pages/Admin/AddUserModal.jsx b/src/pages/Admin/AddUserModal.jsx
index 2bbae05..6579acb 100644
--- a/src/pages/Admin/AddUserModal.jsx
+++ b/src/pages/Admin/AddUserModal.jsx
@@ -219,7 +219,7 @@ function AddUserModal({ onClose, onAdd }) {
try {
// Crear nuevo usuario
- const nuevoUsuario = await agregarUsuario({
+ await agregarUsuario({
idInstitucional: parseInt(formData.idInstitucional),
nombre: formData.nombre,
apellido: formData.apellido,
@@ -230,7 +230,7 @@ function AddUserModal({ onClose, onAdd }) {
// Notificar al componente padre sobre la creación exitosa
if (onAdd) {
- onAdd(nuevoUsuario);
+ onAdd();
}
onClose(); // Cerrar modal tras guardar
diff --git a/src/pages/Admin/EditUserModal.jsx b/src/pages/Admin/EditUserModal.jsx
index 41c1e48..0a4751d 100644
--- a/src/pages/Admin/EditUserModal.jsx
+++ b/src/pages/Admin/EditUserModal.jsx
@@ -209,7 +209,7 @@ function EditUserModal({ user, onClose, onUpdate }) {
try {
// Solo enviamos los campos que deseamos actualizar
- const usuarioActualizado = await actualizarInformacionUsuario(
+ await actualizarInformacionUsuario(
formData.idInstitucional,
{
nombre: formData.nombre,
@@ -222,7 +222,7 @@ function EditUserModal({ user, onClose, onUpdate }) {
// Notificar al componente padre sobre la actualización exitosa
if (onUpdate) {
- onUpdate(usuarioActualizado || { ...user, ...formData });
+ onUpdate();
}
onClose(); // Cerrar modal tras guardar cambios
diff --git a/src/pages/Admin/GestionarUsuarios.jsx b/src/pages/Admin/GestionarUsuarios.jsx
index dc87715..e9ac699 100644
--- a/src/pages/Admin/GestionarUsuarios.jsx
+++ b/src/pages/Admin/GestionarUsuarios.jsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useCallback } from 'react';
import styled from 'styled-components';
import { consultarUsuarios } from '../../api/usuario';
import UserFilters from '../../components/UserFilters';
@@ -130,38 +130,39 @@ function GestionarUsuarios() {
isAdmin: null // null = sin filtro, true = admins, false = no admins
});
- // Efecto para cargar usuarios con los filtros aplicados
- useEffect(() => {
- const loadUsers = async () => {
- setLoading(true);
- setError(null);
+ // Manejador para cargar usuarios
+ const loadUsers = useCallback(async () => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ // Obtener usuarios con filtros
+ const data = await consultarUsuarios(filters);
- try {
- // Obtener usuarios con filtros
- const data = await consultarUsuarios(filters);
-
- // Filtrar por término de búsqueda si existe
- let filteredUsers = data || [];
- if (searchTerm) {
- const searchLower = searchTerm.toLowerCase();
- filteredUsers = filteredUsers.filter(user =>
- user.nombre?.toLowerCase().includes(searchLower) ||
- user.apellido?.toLowerCase().includes(searchLower) ||
- user.correoInstitucional?.toLowerCase().includes(searchLower)
- );
- }
-
- setUsers(filteredUsers);
- } catch (err) {
- console.error("Error al cargar usuarios:", err);
- setError(err.message || "No se pudieron cargar los usuarios");
- } finally {
- setLoading(false);
+ // Filtrar por término de búsqueda si existe
+ let filteredUsers = data || [];
+ if (searchTerm) {
+ const searchLower = searchTerm.toLowerCase();
+ filteredUsers = filteredUsers.filter(user =>
+ user.nombre?.toLowerCase().includes(searchLower) ||
+ user.apellido?.toLowerCase().includes(searchLower) ||
+ user.correoInstitucional?.toLowerCase().includes(searchLower)
+ );
}
- };
-
+
+ setUsers(filteredUsers);
+ } catch (err) {
+ console.error("Error al cargar usuarios:", err);
+ setError(err.message || "No se pudieron cargar los usuarios");
+ } finally {
+ setLoading(false);
+ }
+ }, [filters, searchTerm]);
+
+ // Efecto para cargar usuarios con los filtros aplicados
+ useEffect(() => {
loadUsers();
- }, [filters, searchTerm]); // Re-fetch cuando cambian los filtros o el término de búsqueda
+ }, [loadUsers]);
// Manejador para la búsqueda
const handleSearch = (e) => {
@@ -170,19 +171,21 @@ function GestionarUsuarios() {
};
// Manejador para añadir un nuevo usuario
- const handleAddUser = (newUser) => {
- setUsers(prevUsers => [...prevUsers, newUser]);
+ const handleAddUser = async () => {
+ try {
+ await loadUsers();
+ } catch (err) {
+ console.error("Error actualizando usuarios tras añadir:", err);
+ }
};
// Manejador para actualizar un usuario existente
- const handleUpdateUser = (updatedUser) => {
- setUsers(prevUsers =>
- prevUsers.map(user =>
- user.idInstitucional === updatedUser.idInstitucional
- ? updatedUser
- : user
- )
- );
+ const handleUpdateUser = async () => {
+ try {
+ await loadUsers();
+ } catch (err) {
+ console.error("Error actualizando usuarios tras editar:", err);
+ }
};
return (
diff --git a/src/pages/Administrator/consultaModal/ConsultaRangoFechas.jsx b/src/pages/Administrator/consultaModal/ConsultaRangoFechas.jsx
index d8c7ab1..a03ec4f 100644
--- a/src/pages/Administrator/consultaModal/ConsultaRangoFechas.jsx
+++ b/src/pages/Administrator/consultaModal/ConsultaRangoFechas.jsx
@@ -16,16 +16,30 @@ const ConsultaRangoFechas = () => {
setErrorMsg("Por favor, selecciona ambas fechas.");
return;
}
- // Se llama al endpoint con los parámetros fechaInicio y fechaFin
- const data = await getReservas({
- fechaInicio: filtros.fechaInicio,
- fechaFin: filtros.fechaFin
- });
- if (!data || data.length === 0) {
- setErrorMsg("No se encontraron reservas en el rango de fechas seleccionado.");
- } else {
- setReservas(data);
+
+ const fechaInicio = new Date(filtros.fechaInicio);
+ const fechaFin = new Date(filtros.fechaFin);
+
+ if (fechaInicio > fechaFin) {
+ setErrorMsg("La fecha de inicio no puede ser mayor a la fecha de fin.");
+ return;
}
+
+ let currentDate = new Date(fechaInicio);
+ const fechasConsulta = [];
+ while (currentDate <= fechaFin) {
+ fechasConsulta.push(currentDate.toISOString().split("T")[0]);
+ currentDate.setDate(currentDate.getDate() + 1);
+ }
+
+ const reservasPromises = fechasConsulta.map(fecha =>
+ getReservas({ fecha })
+ );
+ const resultadosDiarios = await Promise.all(reservasPromises);
+
+ const reservasAcumuladas = resultadosDiarios.flat();
+
+ setReservas(reservasAcumuladas);
} catch (error) {
setErrorMsg(error.message || "Error consultando reservas");
}
diff --git a/src/pages/Salones/GestionarSalones.jsx b/src/pages/Salones/GestionarSalones.jsx
index 55c6a9b..6720fc4 100644
--- a/src/pages/Salones/GestionarSalones.jsx
+++ b/src/pages/Salones/GestionarSalones.jsx
@@ -13,19 +13,19 @@ function GestionarSalones({ user }) {
const [searchTerm, setSearchTerm] = useState("");
const [popup, setPopup] = useState({tipo: ""});
const [newSalon, setNewSalon] = useState({
- mnemonic: "",
- name: "",
- description: "",
- location: "",
- capacity: 0,
- resources: []
+ mnemonico: "",
+ nombre: "",
+ descripcion: "",
+ ubicacion: "",
+ capacidad: 0,
+ recursos: []
});
const abrirPopup = (tipo, salon = null) => {
if (tipo === "editar-salon" && salon) {
setNewSalon(salon);
} else if (tipo === "agregar-salon") {
- setNewSalon({ mnemonic: "", name: "", description: "", location: "", capacity: 0, resources: [] });
+ setNewSalon({ mnemonico: "", nombre: "", descripcion: "", ubicacion: "", capacidad: 0, recursos: [] });
}
setPopup({ tipo });
};
@@ -89,16 +89,22 @@ function GestionarSalones({ user }) {
};
// edita un nuevo salón
- const handleEdit = async () => {
- if (newSalon.nombre.trim() && newSalon.descripcion.trim()) {
+ const handleEdit = async (salon) => {
+ if (salon.nombre.trim() && salon.descripcion.trim()) {
try {
- await actualizarSalon(newSalon.mnemonico, newSalon);
-
- const salonActualizado = await getSalonByMnemonico(newSalon.mnemonico);
+ const formattedSalon = {
+ mnemonic: salon.mnemonico,
+ name: salon.nombre,
+ description: salon.descripcion,
+ location: salon.ubicacion,
+ capacity: salon.capacidad,
+ resources: salon.recursos || [],
+ };
+ await actualizarSalon(salon.mnemonico, formattedSalon);
+ const salonActualizado = await getSalonByMnemonico(salon.mnemonico);
if (salonActualizado) {
- setSalones((prevSalones) => prevSalones.map(s => s.mnemonico === newSalon.mnemonico ? salonActualizado : s));
+ setSalones((prevSalones) => prevSalones.map(s => s.mnemonico === salon.mnemonico ? salonActualizado : s));
}
-
cerrarPopup();
} catch (error) {
console.error("Error al editar el salón", error);