From b66d6cdff28ccd58ba208a93ffd922d24cbf0007 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 19 Feb 2026 20:07:40 -0500 Subject: [PATCH 01/46] cambios isaac --- src/pages/Home.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index a43bb4a..30624b8 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -2,7 +2,7 @@ function Home(){ return(

Inicio

-

Bienvenido a nuestra app web con React

+

Bienvenido a nuestra app web con React isaac

) } From 2dafb56475a07874d0d24ad87b4fe79fc5a12bfd Mon Sep 17 00:00:00 2001 From: JoseG Date: Thu, 19 Feb 2026 14:09:35 -1100 Subject: [PATCH 02/46] cambios jose en home --- src/pages/Home.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index a43bb4a..664e63f 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -2,7 +2,7 @@ function Home(){ return(

Inicio

-

Bienvenido a nuestra app web con React

+

Bienvenido a nuestra app web con React joseeee

) } From ff80da17441c9a316bef356a9c7c7775bcc8d5a9 Mon Sep 17 00:00:00 2001 From: emilio Date: Thu, 19 Feb 2026 20:12:53 -0500 Subject: [PATCH 03/46] Cambios en Clock UI - Emilio --- src/pages/Home.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index a43bb4a..80dde86 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -2,7 +2,7 @@ function Home(){ return(

Inicio

-

Bienvenido a nuestra app web con React

+

Bienvenido a nuestra app web con React Emilio

) } From 3e4a6710335279a0f73f155f4d18b22c8259b23a Mon Sep 17 00:00:00 2001 From: Alejandro Arias <00835181@red.unid.mx> Date: Thu, 19 Feb 2026 20:15:29 -0500 Subject: [PATCH 04/46] cambios reports alejandro --- src/pages/Home.jsx | 2 +- src/pages/Reports.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index a43bb4a..189738f 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -2,7 +2,7 @@ function Home(){ return(

Inicio

-

Bienvenido a nuestra app web con React

+

Bienvenido a nuestra app web con React alejandro

) } diff --git a/src/pages/Reports.jsx b/src/pages/Reports.jsx index a3542af..0dce81c 100644 --- a/src/pages/Reports.jsx +++ b/src/pages/Reports.jsx @@ -9,7 +9,7 @@ function Reports() {

- Reporte Mensual + Reporte Mensual alejandro

Asistencia general por mes. From 4d291200c7d803ee9191ce67edc99297c6777697 Mon Sep 17 00:00:00 2001 From: Nestor Alejandro <00856217@red.unid.mx> Date: Fri, 20 Feb 2026 16:54:30 -0500 Subject: [PATCH 05/46] cambios en sidebar-Chiquil --- package-lock.json | 10 +++ package.json | 1 + src/components/Sidebar.jsx | 136 ++++++++++++++++++++++++++++++------- src/pages/Home.jsx | 2 +- 4 files changed, 122 insertions(+), 27 deletions(-) diff --git a/package-lock.json b/package-lock.json index 793b45c..d884077 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "react-intro", "version": "0.0.0", "dependencies": { + "@heroicons/react": "^2.2.0", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router-dom": "^7.13.0" @@ -921,6 +922,15 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@heroicons/react": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz", + "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16 || ^19.0.0-rc" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", diff --git a/package.json b/package.json index 5f05d3a..a96a7fc 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@heroicons/react": "^2.2.0", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router-dom": "^7.13.0" diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx index db6cea9..5e8e8fb 100644 --- a/src/components/Sidebar.jsx +++ b/src/components/Sidebar.jsx @@ -1,39 +1,123 @@ -import {Link} from 'react-router-dom' +import { useState } from 'react'; +import { Link } from 'react-router-dom'; +// Importamos los iconos de Heroicons +// IMPORTANTE: Este módulo usa Heroicons. +// Si ves un error de "module not found", ejecuta en la terminal: +// npm install @heroicons/react +import { + HomeIcon, + InformationCircleIcon, + PhoneIcon, + Squares2X2Icon, + UserGroupIcon, + ClockIcon, + DocumentChartBarIcon, + Bars3Icon, + MagnifyingGlassIcon, + ArrowLeftOnRectangleIcon, + UserCircleIcon +} from '@heroicons/react/24/outline'; -function Sidebar(){ - return( -

diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx index db6cea9..02e4a97 100644 --- a/src/components/Sidebar.jsx +++ b/src/components/Sidebar.jsx @@ -23,6 +23,7 @@ function Sidebar(){ Maestros Reloj Reportes + Configuración diff --git a/src/pages/systemConfig/SystemConfig.jsx b/src/pages/systemConfig/SystemConfig.jsx new file mode 100644 index 0000000..4784c8b --- /dev/null +++ b/src/pages/systemConfig/SystemConfig.jsx @@ -0,0 +1,752 @@ + +import { useState } from "react"; + +// ─── Paleta de colores y estilos base (CSS-in-JS para no afectar estilos globales) ─── +const styles = { + // Layout principal + wrapper: { + fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif", + background: "#ffffff", + minHeight: "100vh", + padding: "2rem", + color: "#1f2937", + }, + header: { + marginBottom: "2.5rem", + borderBottom: "1px solid rgba(0,0,0,0.1)", + paddingBottom: "1.5rem", + }, + headerTitle: { + fontSize: "2rem", + fontWeight: "700", + letterSpacing: "-0.02em", + color: "#1f2937", + margin: "0 0 0.25rem 0", + }, + headerSubtitle: { + fontSize: "0.95rem", + color: "#6b7280", + margin: 0, + fontStyle: "italic", + }, + moduleBadge: { + display: "inline-block", + background: "rgba(59,130,246,0.1)", + border: "1px solid rgba(59,130,246,0.3)", + color: "#1e40af", + fontSize: "0.75rem", + padding: "0.2rem 0.75rem", + borderRadius: "999px", + marginBottom: "0.75rem", + letterSpacing: "0.08em", + textTransform: "uppercase", + }, + + // Grid de secciones + grid: { + display: "grid", + gridTemplateColumns: "repeat(auto-fit, minmax(480px, 1fr))", + gap: "1.5rem", + }, + gridFull: { + display: "grid", + gridTemplateColumns: "1fr", + gap: "1.5rem", + }, + + // Cards + card: { + background: "#f9fafb", + border: "1px solid rgba(0,0,0,0.08)", + borderRadius: "16px", + padding: "1.75rem", + backdropFilter: "blur(12px)", + boxShadow: "0 1px 3px rgba(0,0,0,0.08)", + }, + cardTitle: { + fontSize: "1rem", + fontWeight: "600", + color: "#1f2937", + margin: "0 0 0.25rem 0", + display: "flex", + alignItems: "center", + gap: "0.5rem", + }, + cardDesc: { + fontSize: "0.82rem", + color: "#6b7280", + marginBottom: "1.25rem", + margin: "0 0 1.25rem 0", + }, + + // Formularios + label: { + display: "block", + fontSize: "0.8rem", + color: "#374151", + marginBottom: "0.35rem", + letterSpacing: "0.04em", + textTransform: "uppercase", + }, + input: { + width: "100%", + background: "#ffffff", + border: "1px solid rgba(0,0,0,0.12)", + borderRadius: "8px", + padding: "0.6rem 0.9rem", + color: "#1f2937", + fontSize: "0.9rem", + outline: "none", + boxSizing: "border-box", + transition: "border-color 0.2s", + }, + select: { + width: "100%", + background: "#ffffff", + border: "1px solid rgba(0,0,0,0.12)", + borderRadius: "8px", + padding: "0.6rem 0.9rem", + color: "#1f2937", + fontSize: "0.9rem", + outline: "none", + boxSizing: "border-box", + }, + fieldGroup: { + marginBottom: "1rem", + }, + row: { + display: "grid", + gridTemplateColumns: "1fr 1fr", + gap: "1rem", + }, + + // Botones + btnPrimary: { + background: "linear-gradient(135deg, #3b82f6, #1d4ed8)", + color: "#fff", + border: "none", + borderRadius: "8px", + padding: "0.55rem 1.4rem", + fontSize: "0.85rem", + fontWeight: "600", + cursor: "pointer", + transition: "opacity 0.2s, transform 0.1s", + letterSpacing: "0.02em", + }, + btnSecondary: { + background: "#ffffff", + color: "#374151", + border: "1px solid rgba(0,0,0,0.15)", + borderRadius: "8px", + padding: "0.55rem 1.2rem", + fontSize: "0.85rem", + cursor: "pointer", + transition: "background 0.2s", + }, + btnDanger: { + background: "rgba(239,68,68,0.1)", + color: "#dc2626", + border: "1px solid rgba(239,68,68,0.3)", + borderRadius: "8px", + padding: "0.4rem 0.9rem", + fontSize: "0.8rem", + cursor: "pointer", + }, + btnActions: { + display: "flex", + gap: "0.75rem", + marginTop: "1.25rem", + justifyContent: "flex-end", + }, + + // Slider + sliderWrapper: { + marginBottom: "1rem", + }, + sliderRow: { + display: "flex", + justifyContent: "space-between", + alignItems: "center", + marginBottom: "0.4rem", + }, + sliderValue: { + background: "rgba(59,130,246,0.1)", + color: "#1e40af", + padding: "0.1rem 0.6rem", + borderRadius: "999px", + fontSize: "0.85rem", + fontWeight: "700", + }, + slider: { + width: "100%", + accentColor: "#3b82f6", + cursor: "pointer", + }, + + // Tabla de roles + table: { + width: "100%", + borderCollapse: "collapse", + fontSize: "0.88rem", + }, + th: { + textAlign: "left", + padding: "0.6rem 0.8rem", + color: "#374151", + fontSize: "0.75rem", + textTransform: "uppercase", + letterSpacing: "0.06em", + borderBottom: "1px solid rgba(0,0,0,0.08)", + }, + td: { + padding: "0.75rem 0.8rem", + borderBottom: "1px solid rgba(0,0,0,0.04)", + color: "#374151", + verticalAlign: "middle", + }, + + // Badges de estado + badgeActive: { + background: "rgba(34,197,94,0.1)", + color: "#159b2b", + border: "1px solid rgba(34,197,94,0.2)", + borderRadius: "999px", + padding: "0.15rem 0.65rem", + fontSize: "0.75rem", + fontWeight: "600", + }, + badgeInactive: { + background: "rgba(0,0,0,0.05)", + color: "#6b7280", + border: "1px solid rgba(0,0,0,0.1)", + borderRadius: "999px", + padding: "0.15rem 0.65rem", + fontSize: "0.75rem", + }, + badgeAdmin: { + background: "rgba(168,85,247,0.1)", + color: "#7e22ce", + border: "1px solid rgba(168,85,247,0.2)", + borderRadius: "999px", + padding: "0.15rem 0.65rem", + fontSize: "0.75rem", + fontWeight: "600", + }, + + // Toggle switch + toggleRow: { + display: "flex", + justifyContent: "space-between", + alignItems: "center", + padding: "0.7rem 0", + borderBottom: "1px solid rgba(0,0,0,0.05)", + }, + toggleLabel: { + fontSize: "0.88rem", + color: "#1f2937", + }, + toggleSub: { + fontSize: "0.78rem", + color: "#9ca3af", + marginTop: "0.1rem", + }, + + // Horario + scheduleGrid: { + display: "grid", + gridTemplateColumns: "repeat(7, 1fr)", + gap: "0.4rem", + marginBottom: "1rem", + }, + dayCell: { + textAlign: "center", + padding: "0.5rem 0.25rem", + borderRadius: "8px", + fontSize: "0.78rem", + cursor: "pointer", + border: "1px solid transparent", + transition: "all 0.2s", + fontWeight: "600", + }, + dayCellActive: { + background: "rgba(59,130,246,0.1)", + border: "1px solid rgba(59,130,246,0.3)", + color: "#1e40af", + }, + dayCellInactive: { + background: "#f3f4f6", + border: "1px solid rgba(0,0,0,0.08)", + color: "#9ca3af", + }, + + // Divider + divider: { + borderTop: "1px solid rgba(0,0,0,0.06)", + margin: "1.25rem 0", + }, + + // Icono decorativo en card header + iconBox: { + width: "32px", + height: "32px", + borderRadius: "8px", + display: "flex", + alignItems: "center", + justifyContent: "center", + fontSize: "1rem", + flexShrink: 0, + }, +}; + +// ─── Datos mock ───────────────────────────────────────────────────────────────── + +const MOCK_ROLES = [ + { id: 1, nombre: "Administrador", permisos: "Total", usuarios: 3, estado: "activo" }, + { id: 2, nombre: "Coordinador", permisos: "Parcial", usuarios: 8, estado: "activo" }, + { id: 3, nombre: "Docente", permisos: "Lectura", usuarios: 42, estado: "activo" }, + { id: 4, nombre: "Auxiliar", permisos: "Limitado", usuarios: 12, estado: "inactivo" }, +]; + +const DIAS_SEMANA = ["Lun", "Mar", "Mié", "Jue", "Vie", "Sáb", "Dom"]; + +// ─── Subcomponente: Toggle ──────────────────────────────────────────────────── + +function Toggle({ checked, onChange }) { + return ( +
onChange(!checked)} + style={{ + width: "42px", + height: "22px", + borderRadius: "999px", + background: checked ? "rgba(59,130,246,0.8)" : "rgba(209,213,219,0.8)", + border: checked ? "1px solid rgba(59,130,246,0.6)" : "1px solid rgba(0,0,0,0.12)", + position: "relative", + cursor: "pointer", + transition: "background 0.2s", + flexShrink: 0, + }} + > +
+
+ ); +} + +// ─── Subcomponente: SectionCard ──────────────────────────────────────────────── + +function SectionCard({ icon, title, desc, color = "#3b82f6", children }) { + return ( +
+
+
+ {icon} +
+
+

{title}

+

{desc}

+
+
+ {children} +
+ ); +} + +// ─── Subcomponente: Badge de rol ─────────────────────────────────────────────── + +function RolBadge({ permisos }) { + if (permisos === "Total") return {permisos}; + if (permisos === "Parcial") return {permisos}; + return {permisos}; +} + +// ─── COMPONENTE PRINCIPAL ────────────────────────────────────────────────────── + +export default function SystemConfig() { + + // --- Estado: Ajustes Institucionales --- + const [institucion, setInstitucion] = useState({ + nombre: "Instituto Tecnológico Nacional", + codigo: "ITN-2024", + zona: "America/Mexico_City", + idioma: "es", + }); + + // --- Estado: Tolerancia --- + const [tolerancia, setTolerancia] = useState({ + entrada: 10, + salida: 5, + inasistencias: 3, + }); + + // --- Estado: Horario base --- + const [diasActivos, setDiasActivos] = useState([true, true, true, true, true, false, false]); + const [horaInicio, setHoraInicio] = useState("07:00"); + const [horaFin, setHoraFin] = useState("18:00"); + const [duracionBloque, setDuracionBloque] = useState("60"); + + // --- Estado: Roles --- + const [roles, setRoles] = useState(MOCK_ROLES); + + // --- Estado: Notificaciones --- + const [notificaciones, setNotificaciones] = useState({ + emailAsistencia: true, + emailReportes: true, + pushAlertas: false, + resumenDiario: true, + alertasFaltas: true, + notifDocentes: false, + }); + + // --- Handlers --- + const toggleDia = (idx) => { + const copia = [...diasActivos]; + copia[idx] = !copia[idx]; + setDiasActivos(copia); + }; + + const toggleRolEstado = (id) => { + setRoles(roles.map((r) => + r.id === id ? { ...r, estado: r.estado === "activo" ? "inactivo" : "activo" } : r + )); + }; + + const toggleNotif = (key) => { + setNotificaciones((prev) => ({ ...prev, [key]: !prev[key] })); + }; + + // ─── RENDER ──────────────────────────────────────────────────────────────── + + return ( +
+ + {/* ── Encabezado del módulo ── */} +
+

Configuración del Sistema

+

+ Administre los parámetros globales de operación institucional. +

+
+ + {/* ══════════════════════════════════════════════════════ + SECCIÓN 1 & 2: Ajustes Institucionales + Tolerancia + ══════════════════════════════════════════════════════ */} +
+ + {/* ── 1. Ajustes Institucionales ── */} + +
+ + setInstitucion({ ...institucion, nombre: e.target.value })} + /> +
+ +
+
+ + setInstitucion({ ...institucion, codigo: e.target.value })} + /> +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+
+ + {/* ── 2. Configuración de Tolerancia ── */} + + {/* Slider: tolerancia de entrada */} +
+
+ + {tolerancia.entrada} min +
+ setTolerancia({ ...tolerancia, entrada: Number(e.target.value) })} + style={styles.slider} + /> +
+ 0 min30 min +
+
+ + {/* Slider: tolerancia de salida */} +
+
+ + {tolerancia.salida} min +
+ setTolerancia({ ...tolerancia, salida: Number(e.target.value) })} + style={styles.slider} + /> +
+ 0 min20 min +
+
+ +
+ +
+ + +
+ + {/* Indicador visual */} +
+ ⚠️ +

+ Los cambios en tolerancia afectarán los registros futuros. Los datos históricos no se modificarán. +

+
+ +
+ + +
+ +
+ + {/* ══════════════════════════════════════════════════════ + SECCIÓN 3: Horario Base + ══════════════════════════════════════════════════════ */} +
+ +

Días laborables activos

+
+ {DIAS_SEMANA.map((dia, idx) => ( +
toggleDia(idx)} + style={{ + ...styles.dayCell, + ...(diasActivos[idx] ? styles.dayCellActive : styles.dayCellInactive), + }} + > +
{dia}
+
+ {diasActivos[idx] ? "✓" : "—"} +
+
+ ))} +
+ +
+ +
+
+ + setHoraInicio(e.target.value)} + /> +
+
+ + setHoraFin(e.target.value)} + /> +
+
+ + +
+
+ +
+ + +
+ +
+ + {/* ══════════════════════════════════════════════════════ + SECCIÓN 4 & 5: Roles + Notificaciones + ══════════════════════════════════════════════════════ */} +
+ + {/* ── 4. Gestión de Roles ── */} + + + + + + + + + + + + + {roles.map((rol) => ( + + + + + + + + ))} + +
RolPermisosUsuariosEstadoAcción
+ {rol.nombre} + + + + {rol.usuarios} + + {rol.estado === "activo" + ? Activo + : Inactivo + } + + +
+ +
+ +
+
+ + {/* ── 5. Preferencias de Notificación ── */} + + {[ + { key: "emailAsistencia", label: "Notificaciones de asistencia", sub: "Email al registrar una asistencia" }, + { key: "emailReportes", label: "Reportes periódicos", sub: "Resúmenes semanales por correo" }, + { key: "pushAlertas", label: "Alertas push", sub: "Notificaciones en el navegador" }, + { key: "resumenDiario", label: "Resumen diario", sub: "Email con el resumen del día" }, + { key: "alertasFaltas", label: "Alertas por faltas excesivas", sub: "Notificar al superar el límite" }, + { key: "notifDocentes", label: "Notificar a docentes", sub: "Avisar al docente sobre ausencias" }, + ].map(({ key, label, sub }) => ( +
+
+
{label}
+
{sub}
+
+ toggleNotif(key)} + /> +
+ ))} + +
+ + +
+
+
+ + +
+ ); +} \ No newline at end of file From 88a6642a2c12c69ceee5ce5ca71c2bdd6bbc897d Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 26 Feb 2026 18:47:10 -0500 Subject: [PATCH 08/46] =?UTF-8?q?Modificaci=C3=B3n=20de=20Teachers-Isaac?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 10 + package.json | 1 + src/components/Sidebar.jsx | 2 +- src/pages/Teachers.jsx | 406 +++++++++++++++++++++++++++++++++++-- 4 files changed, 397 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index 793b45c..d884077 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "react-intro", "version": "0.0.0", "dependencies": { + "@heroicons/react": "^2.2.0", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router-dom": "^7.13.0" @@ -921,6 +922,15 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@heroicons/react": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz", + "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16 || ^19.0.0-rc" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", diff --git a/package.json b/package.json index 5f05d3a..a96a7fc 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@heroicons/react": "^2.2.0", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router-dom": "^7.13.0" diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx index db6cea9..ac808b8 100644 --- a/src/components/Sidebar.jsx +++ b/src/components/Sidebar.jsx @@ -36,4 +36,4 @@ function Sidebar(){ ) } -export default Sidebar \ No newline at end of file +export default Sidebar; \ No newline at end of file diff --git a/src/pages/Teachers.jsx b/src/pages/Teachers.jsx index c6ecaad..2d775ac 100644 --- a/src/pages/Teachers.jsx +++ b/src/pages/Teachers.jsx @@ -1,26 +1,390 @@ -function Teachers() { +import { useState } from "react"; + +const initialTeachers = [ + { id: 1, name: "Leydi Xequeb", subject: "Matemáticas", email: "l.xequeb@universidad.edu", phone: "+502 5555-0101", status: "Activo", degree: "MSc. Matemáticas Aplicadas", joined: "2019-03-15", avatar: "LX" }, + { id: 2, name: "Irvin Chan", subject: "Programación", email: "i.chan@universidad.edu", phone: "+502 5555-0202", status: "Activo", degree: "Ing. en Sistemas", joined: "2020-08-01", avatar: "IC" }, + { id: 3, name: "Marcos Rivera", subject: "Fe y Mundo", email: "m.rivera@universidad.edu", phone: "+502 5555-0303", status: "Activo", degree: "Lic. Teología", joined: "2018-01-20", avatar: "MR" }, + { id: 4, name: "Sandra López", subject: "Física", email: "s.lopez@universidad.edu", phone: "+502 5555-0404", status: "Inactivo", degree: "PhD. Física Teórica", joined: "2017-06-10", avatar: "SL" }, + { id: 5, name: "Carlos Menéndez", subject: "Química", email: "c.menendez@universidad.edu", phone: "+502 5555-0505", status: "Activo", degree: "MSc. Química Industrial", joined: "2021-02-28", avatar: "CM" }, + { id: 6, name: "Ana Fuentes", subject: "Inglés", email: "a.fuentes@universidad.edu", phone: "+502 5555-0606", status: "Licencia", degree: "BA. Lenguas Modernas", joined: "2016-09-05", avatar: "AF" }, +]; + +const avatarColors = [ + ["#1E3A8A", "#FBBF24"], + ["#1D4ED8", "#F59E0B"], + ["#1E40AF", "#FCD34D"], + ["#1a3270", "#FBBF24"], + ["#2563EB", "#F59E0B"], + ["#1E3A8A", "#FDE68A"], +]; + +const statusConfig = { + Activo: { bg: "#D1FAE5", color: "#065F46", dot: "#10B981" }, + Inactivo: { bg: "#FEE2E2", color: "#991B1B", dot: "#EF4444" }, + Licencia: { bg: "#FEF3C7", color: "#92400E", dot: "#F59E0B" }, +}; + +export default function Teachers() { + const [teachers, setTeachers] = useState(initialTeachers); + const [search, setSearch] = useState(""); + const [statusFilter, setStatusFilter] = useState("Todos"); + const [showModal, setShowModal] = useState(false); + const [modalMode, setModalMode] = useState("add"); // "add" | "view" | "edit" + const [selectedTeacher, setSelectedTeacher] = useState(null); + const [form, setForm] = useState({ name: "", subject: "", email: "", phone: "", degree: "", status: "Activo" }); + const [sortField, setSortField] = useState("name"); + const [sortDir, setSortDir] = useState("asc"); + + const filtered = teachers + .filter(t => statusFilter === "Todos" || t.status === statusFilter) + .filter(t => + t.name.toLowerCase().includes(search.toLowerCase()) || + t.subject.toLowerCase().includes(search.toLowerCase()) || + t.email.toLowerCase().includes(search.toLowerCase()) + ) + .sort((a, b) => { + const va = a[sortField] || ""; const vb = b[sortField] || ""; + return sortDir === "asc" ? va.localeCompare(vb) : vb.localeCompare(va); + }); + + const handleSort = (field) => { + if (sortField === field) setSortDir(d => d === "asc" ? "desc" : "asc"); + else { setSortField(field); setSortDir("asc"); } + }; + + const openAdd = () => { setModalMode("add"); setForm({ name: "", subject: "", email: "", phone: "", degree: "", status: "Activo" }); setShowModal(true); }; + const openView = (t) => { setModalMode("view"); setSelectedTeacher(t); setShowModal(true); }; + const openEdit = (t) => { setModalMode("edit"); setSelectedTeacher(t); setForm({ name: t.name, subject: t.subject, email: t.email, phone: t.phone, degree: t.degree, status: t.status }); setShowModal(true); }; + + const handleSave = () => { + if (!form.name.trim()) return; + if (modalMode === "edit") { + // Editar docente existente + setTeachers(prev => prev.map(t => t.id === selectedTeacher.id ? { ...selectedTeacher, ...form } : t)); + } else { + // Agregar nuevo docente + const initials = form.name.split(" ").map(w => w[0]).join("").slice(0, 2).toUpperCase(); + setTeachers(prev => [...prev, { ...form, id: Date.now(), avatar: initials, joined: new Date().toISOString().split("T")[0] }]); + } + setShowModal(false); + }; + + const handleDelete = (id) => { + if (window.confirm("¿Estás seguro de que deseas eliminar este docente?")) { + setTeachers(prev => prev.filter(t => t.id !== id)); + } + }; + + const SortIcon = ({ field }) => ( + + {sortField === field && sortDir === "desc" ? "▼" : "▲"} + + ); + return ( -
-

Maestros

-
-
-

Leydi Xequeb

-

Matemáticas

-

Activo

+
+ + + {/* Header */} +
+
+
+
+ + + +
+

+ Gestión de Docentes +

-
-

Irvin Chan

-

Programacion

-

Activo

+

+ Sistema Académico Universitario · {teachers.length} docentes registrados +

+
+ +
+ + {/* Stats bar */} +
+ {[ + { label: "Total Docentes", value: teachers.length, icon: "👥", accent: "#1E3A8A" }, + { label: "Activos", value: teachers.filter(t => t.status === "Activo").length, icon: "✅", accent: "#059669" }, + { label: "Inactivos", value: teachers.filter(t => t.status === "Inactivo").length, icon: "⛔", accent: "#DC2626" }, + { label: "En Licencia", value: teachers.filter(t => t.status === "Licencia").length, icon: "📋", accent: "#D97706" }, + ].map((s, i) => ( +
+
{s.label}
+
+ {s.value} + {s.icon} +
+
+ ))} +
+ + {/* Search & Filter bar */} +
+ {/* Search */} +
+ + setSearch(e.target.value)} + placeholder="Buscar por nombre, materia o correo..." + style={{ width: "100%", paddingLeft: 38, paddingRight: 14, height: 40, border: "1.5px solid #E2E8F0", borderRadius: 9, fontSize: 13.5, color: "#334155", background: "#F8FAFC" }} + /> +
+ + {/* Status filter */} +
+ {["Todos", "Activo", "Inactivo", "Licencia"].map(s => ( + + ))} +
+ + {/* Results count */} +
+ {filtered.length} resultado{filtered.length !== 1 ? "s" : ""} +
+
+ + {/* Table */} +
+
+ + + + {[ + { label: "Docente", field: "name" }, + { label: "Materia", field: "subject" }, + { label: "Correo", field: "email" }, + { label: "Teléfono", field: null }, + { label: "Estado", field: "status" }, + { label: "Ingreso", field: "joined" }, + { label: "Acciones", field: null }, + ].map((col, i) => ( + + ))} + + + + {filtered.length === 0 ? ( + + + + ) : filtered.map((t, idx) => { + const sc = statusConfig[t.status] || statusConfig.Activo; + const ac = avatarColors[t.id % avatarColors.length]; + return ( + + {/* Docente */} + + {/* Materia */} + + {/* Email */} + + {/* Phone */} + + {/* Status */} + + {/* Joined */} + + {/* Actions */} + + + ); + })} + +
col.field && handleSort(col.field)} + className={col.field ? "th-btn" : ""} + style={{ + padding: "13px 16px", textAlign: "left", fontSize: 11.5, fontWeight: 700, + color: "#FBBF24", letterSpacing: "0.08em", textTransform: "uppercase", + cursor: col.field ? "pointer" : "default", userSelect: "none", + borderBottom: "2px solid rgba(251,191,36,0.3)", whiteSpace: "nowrap" + }} + > + {col.label}{col.field && } +
+
🔍
+
Sin resultados
+
Intenta cambiar el filtro o la búsqueda
+
+
+
+ {t.avatar} +
+
+
{t.name}
+
{t.degree}
+
+
+
+ {t.subject} + {t.email}{t.phone} + + + {t.status} + + {t.joined} +
+ + + +
+
+
+ + {/* Table footer */} +
+ Mostrando {filtered.length} de {teachers.length} docentes +
+ {[1].map(p => ( + + ))} +
+
+
+ + {/* Modal */} + {showModal && ( +
setShowModal(false)} style={{ position: "fixed", inset: 0, background: "rgba(15,23,42,0.55)", backdropFilter: "blur(4px)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 1000, padding: 20 }}> +
e.stopPropagation()} style={{ background: "white", borderRadius: 18, width: "100%", maxWidth: 520, boxShadow: "0 24px 64px rgba(0,0,0,0.22)", overflow: "hidden" }}> + {/* Modal header */} +
+
+
+ +
+
+

+ {modalMode === "add" ? "Nuevo Docente" : modalMode === "edit" ? "Editar Docente" : selectedTeacher?.name} +

+

+ {modalMode === "add" ? "Completar información del docente" : modalMode === "edit" ? "Actualizar información del docente" : selectedTeacher?.subject} +

+
+
+ +
+ + {/* Modal body */} +
+ {modalMode === "view" && selectedTeacher && !form.name ? ( +
+ {/* Avatar grande */} +
+
+ {selectedTeacher.avatar} +
+
+
{selectedTeacher.name}
+
{selectedTeacher.degree}
+ + + {selectedTeacher.status} + +
+
+
+ {[ + { icon: "📚", label: "Materia", val: selectedTeacher.subject }, + { icon: "📧", label: "Correo", val: selectedTeacher.email }, + { icon: "📞", label: "Teléfono", val: selectedTeacher.phone }, + { icon: "📅", label: "Fecha de Ingreso", val: selectedTeacher.joined }, + ].map((item, i) => ( +
+
{item.icon} {item.label}
+
{item.val}
+
+ ))} +
+
+ ) : ( +
+ {[ + { label: "Nombre Completo", key: "name", type: "text", placeholder: "Ej. María García López" }, + { label: "Materia que imparte", key: "subject", type: "text", placeholder: "Ej. Cálculo Diferencial" }, + { label: "Correo Institucional", key: "email", type: "email", placeholder: "docente@universidad.edu" }, + { label: "Teléfono", key: "phone", type: "text", placeholder: "+502 0000-0000" }, + { label: "Grado Académico", key: "degree", type: "text", placeholder: "Ej. MSc. Ingeniería de Software" }, + ].map(field => ( +
+ + setForm(f => ({ ...f, [field.key]: e.target.value }))} + style={{ width: "100%", height: 40, border: "1.5px solid #E2E8F0", borderRadius: 9, padding: "0 14px", fontSize: 13.5, color: "#334155", background: "#F8FAFC" }} + /> +
+ ))} +
+ + +
+
+ )} +
+ + {/* Modal footer */} +
+ + {(modalMode === "add" || modalMode === "edit") && ( + + )} +
-
-

Marcos Riviera

-

Fe y Mundo

-

Activo

-
-
+
+ )}
- ) + ); } - -export default Teachers; \ No newline at end of file From f5c6446fcd68fa00d0df06129f5f10f3089da1cd Mon Sep 17 00:00:00 2001 From: emilio Date: Thu, 26 Feb 2026 18:48:16 -0500 Subject: [PATCH 09/46] Modificacion Dashboard --- src/pages/Dashboard.jsx | 732 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 711 insertions(+), 21 deletions(-) diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx index a2476e4..b33af0d 100644 --- a/src/pages/Dashboard.jsx +++ b/src/pages/Dashboard.jsx @@ -1,31 +1,721 @@ -function Dashboard() { - return ( -
-

Panel de Control

- {/* Cards */} -
-
-

Maestros Activos

-

60

+import { useState, useEffect } from "react"; + +// ── Paleta oficial ────────────────────────────────────────────── +const C = { + blue: "#1a3a8f", + blueMid: "#2252c8", + blueLight: "#dde9ff", + yellow: "#f5c400", + yellowSoft: "#fff8d6", + white: "#ffffff", + bg: "#f0f4ff", + text: "#0d1f4e", + muted: "#64748b", +}; + +// ── Mock data ─────────────────────────────────────────────────── +const dailyData = [ + { hora: "7am", asistencias: 4 }, + { hora: "8am", asistencias: 12 }, + { hora: "9am", asistencias: 18 }, + { hora: "10am", asistencias: 10 }, + { hora: "11am", asistencias: 7 }, + { hora: "12pm", asistencias: 5 }, + { hora: "1pm", asistencias: 3 }, +]; + +const monthlyData = [ + { mes: "Ene", asistencias: 92, faltas: 8 }, + { mes: "Feb", asistencias: 88, faltas: 12 }, + { mes: "Mar", asistencias: 95, faltas: 5 }, + { mes: "Abr", asistencias: 90, faltas: 10 }, + { mes: "May", asistencias: 85, faltas: 15 }, + { mes: "Jun", asistencias: 78, faltas: 22 }, +]; + +const recentActivity = [ + { nombre: "Leydi Xequeb", materia: "Matemáticas", hora: "08:02 AM", estado: "Entrada", tipo: "entrada" }, + { nombre: "Irvin Chan", materia: "Programación", hora: "08:15 AM", estado: "Entrada", tipo: "entrada" }, + { nombre: "Marcos Riviera", materia: "Fe y Mundo", hora: "09:05 AM", estado: "Retardo", tipo: "retardo" }, + { nombre: "Ana Pérez", materia: "Inglés", hora: "09:30 AM", estado: "Entrada", tipo: "entrada" }, + { nombre: "Luis Méndez", materia: "Historia", hora: "10:00 AM", estado: "Falta", tipo: "falta" }, +]; + +const teachers = [ + { nombre: "Ana Pérez", materia: "Inglés", asistencia: 99, avatar: "AP" }, + { nombre: "Leydi Xequeb", materia: "Matemáticas", asistencia: 97, avatar: "LX" }, + { nombre: "Irvin Chan", materia: "Programación", asistencia: 94, avatar: "IC" }, + { nombre: "Marcos Riviera", materia: "Fe y Mundo", asistencia: 88, avatar: "MR" }, +]; + +// ── Contador animado ──────────────────────────────────────────── +function Counter({ target }) { + const [count, setCount] = useState(0); + useEffect(() => { + let v = 0; + const step = target / 40; + const t = setInterval(() => { + v += step; + if (v >= target) { setCount(target); clearInterval(t); } + else setCount(Math.floor(v)); + }, 28); + return () => clearInterval(t); + }, [target]); + return <>{count}; +} + +// ── Reloj digital ─────────────────────────────────────────────── +function Clock() { + const [time, setTime] = useState(new Date()); + useEffect(() => { + const interval = setInterval(() => setTime(new Date()), 1000); + return () => clearInterval(interval); + }, []); + + const hours = String(time.getHours()).padStart(2, '0'); + const minutes = String(time.getMinutes()).padStart(2, '0'); + const seconds = String(time.getSeconds()).padStart(2, '0'); + + return ( +
+
+ {hours} + : + {minutes} + : + {seconds} +
+
+ ); +} + +// ── Line chart SVG ────────────────────────────────────────────── +function LineChart({ data, valueKey, labelKey, height = 130 }) { + const [hovered, setHovered] = useState(null); + const W = 560, H = height - 24; + const pad = { t: 16, r: 16, b: 8, l: 28 }; + const innerW = W - pad.l - pad.r; + const innerH = H - pad.t - pad.b; + const max = Math.max(...data.map((d) => d[valueKey])); + const min = 0; + + const pts = data.map((d, i) => ({ + x: pad.l + (i / (data.length - 1)) * innerW, + y: pad.t + (1 - (d[valueKey] - min) / (max - min)) * innerH, + val: d[valueKey], + label: d[labelKey], + })); + + // Smooth polyline path + const linePath = pts + .map((p, i) => (i === 0 ? `M ${p.x},${p.y}` : `L ${p.x},${p.y}`)) + .join(" "); + + // Area fill path + const areaPath = + linePath + + ` L ${pts[pts.length - 1].x},${pad.t + innerH} L ${pts[0].x},${pad.t + innerH} Z`; + + // Unique gradient id + const gradId = "lineGrad"; + const areaId = "areaGrad"; + + return ( +
+ + + + + + + + + + + + + {/* Grid lines */} + {[0, 0.25, 0.5, 0.75, 1].map((r, i) => { + const y = pad.t + r * innerH; + return ( + + ); + })} + + {/* Area fill */} + + + {/* Line */} + + + {/* Points + hover */} + {pts.map((p, i) => ( + + {/* Invisible hit area */} + setHovered(i)} + onMouseLeave={() => setHovered(null)} + /> + + {/* Dot */} + + + {/* Tooltip */} + {hovered === i && ( + + + + {p.val} + + {/* Vertical dashed line */} + + + )} + + {/* X label */} + + {p.label} + + + ))} + +
+ ); +} + +// ── Donut chart ───────────────────────────────────────────────── +function DonutChart({ percent, color, size = 72 }) { + const r = 28; + const circ = 2 * Math.PI * r; + const dash = (percent / 100) * circ; + return ( + + + + + {percent}% + + + ); +} + +// ── KPI Card con hover ────────────────────────────────────────── +function KpiCard({ label, value, icon, sub, accent }) { + const [hover, setHover] = useState(false); + return ( +
setHover(true)} + onMouseLeave={() => setHover(false)} + className="rounded-2xl p-5 relative overflow-hidden cursor-default" + style={{ + background: hover + ? `linear-gradient(135deg, ${C.blue} 0%, ${C.blueMid} 100%)` + : C.white, + border: `2px solid ${hover ? C.yellow : C.blueLight}`, + boxShadow: hover + ? `0 8px 32px ${C.blue}40` + : "0 2px 8px rgba(0,0,0,0.06)", + transition: "all 0.25s ease", + }} + > +
+
+
+

+ {label} +

+ + {icon} + +
+

+ +

+

+ {sub} +

+
+
+ ); +} + +// ── DASHBOARD ─────────────────────────────────────────────────── +function Dashboard() { + const [activeTab, setActiveTab] = useState("diario"); + + const now = new Date(); + const dateStr = now.toLocaleDateString("es-MX", { + weekday: "long", year: "numeric", month: "long", day: "numeric", + }); + + return ( +
+ + {/* ── MAIN ── */} +
+ + {/* Header */} +
+
+

+ Panel de Control +

+

{dateStr}

+
+
+
+ + Sistema Activo +
+
+ UNID Playa del Carmen +
+
+
+ + {/* KPI CARDS */} +
+ + + + +
+ + {/* CHARTS */} +
+ + {/* Bar chart */} +
+
+
+

Registros del Día

+

Entradas por hora — hoy

+
+
+ {["diario", "mensual"].map((tab) => ( + + ))} +
+
+ + {activeTab === "diario" ? ( + + ) : ( + + )} + +
+
+
+ Asistencias +
+ {activeTab === "mensual" && ( +
+
+ Faltas +
+ )} +
+
+ + {/* Donuts */} +
+

Indicadores Clave

+

Porcentaje del periodo actual

+
+ {[ + { label: "Tasa de Asistencia", percent: 87, color: C.blueMid }, + { label: "Puntualidad", percent: 94, color: C.yellow }, + { label: "Cobertura Docente", percent: 100, color: "#16a34a" }, + ].map((item, i) => ( +
+ +
+

{item.label}

+

Ene — Jun 2025

+
+ ))} +
+
+
-
-

Asistencias

-

40

+ {/* BOTTOM ROW */} +
+ {/* Reloj */} +
+

Hora Actual

+ +

Tiempo real del sistema

+
+ + {/* Actividad reciente */} +
+
+
+

Actividad Reciente

+

Últimos registros de asistencia

+
+ +
+
+ {recentActivity.map((item, i) => ( +
+
+ {item.nombre.split(" ").map((n) => n[0]).join("").slice(0, 2)} +
+
+

{item.nombre}

+

{item.materia}

+
+
+

{item.hora}

+ + {item.estado} + +
+ ))} +
+
+ + {/* Ranking */} +
+

Ranking Docentes

+

Por tasa de asistencia

-
-

Retardos

-

3

+
+ {teachers.map((t, i) => ( +
+ + #{i + 1} + +
+ {t.avatar} +
+
+
+

{t.nombre}

+

{t.asistencia}%

+
+
+
+
+
+ ))} +
-
-

Faltas

-

1

+ {/* Resumen cuatrimestral */} +
+
+
+

Resumen Cuatrimestral

+
+

Enero — Abril 2025

+
+
+

91%

+

Asistencia

+
+
+

4.2

+

Promedio

-
-
- ) +
+

6

+

Meses

+
+
+
+
+ +
+
+
+ ); } export default Dashboard; \ No newline at end of file From 5072a77c7d75d20f4bf406ed89453c74619e52e6 Mon Sep 17 00:00:00 2001 From: Nestor Alejandro <00856217@red.unid.mx> Date: Thu, 26 Feb 2026 19:11:42 -0500 Subject: [PATCH 10/46] Cambios en sidebar y app --- src/App.jsx | 2 + src/components/Sidebar.jsx | 231 +++++++++++++++++++++---------------- 2 files changed, 131 insertions(+), 102 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 78f28a5..9a2c233 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -9,6 +9,7 @@ import Dashboard from "./pages/Dashboard" import Teachers from "./pages/Teachers" import Clock from "./pages/Clock" import Reports from "./pages/Reports" +import SystemConfig from "./pages/sistemaconfig" function App() { @@ -26,6 +27,7 @@ function App() { } /> } /> } /> + } />
diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx index f8f66d7..794df86 100644 --- a/src/components/Sidebar.jsx +++ b/src/components/Sidebar.jsx @@ -1,126 +1,153 @@ +/** + * INSTRUCCIONES DE INSTALACIÓN: + * 1. Instalar Heroicons: npm install @heroicons/react + * 2. Instalar React Router: npm install react-router-dom + * 3. Tener configurado Tailwind CSS en el proyecto. + * * EXTENSIONES RECOMENDADAS EN VS CODE: + * - Tailwind CSS IntelliSense (para autocompletado de clases) + * - ES7+ React/Redux/React-Native snippets + */ + import { useState } from 'react'; import { Link } from 'react-router-dom'; import { - // Importamos los iconos de Heroicons -// IMPORTANTE: Este módulo usa Heroicons. -// Si ves un error de "module not found", ejecuta en la terminal: -// npm install @heroicons/react - HomeIcon, InformationCircleIcon, PhoneIcon, - Squares2X2Icon, UserGroupIcon, ClockIcon, - DocumentChartBarIcon, Bars3Icon, MagnifyingGlassIcon, - ArrowLeftOnRectangleIcon, UserCircleIcon + HomeIcon, + InformationCircleIcon, + PhoneIcon, + Squares2X2Icon, + UserGroupIcon, + ClockIcon, + DocumentChartBarIcon, + Bars3Icon, + MagnifyingGlassIcon, + ArrowLeftOnRectangleIcon, + UserCircleIcon, + Cog6ToothIcon // Icono de engranaje para Configuración } from '@heroicons/react/24/outline'; -// Sidebar con secciones públicas y administrativas, colapsable, con perfil de usuario, buscador y botón de cerrar sesión function Sidebar() { + // Estado para colapsar/expandir el sidebar const [isCollapsed, setIsCollapsed] = useState(false); + const iconSize = 'h-5 w-5'; - // Corregido: Ahora sí crecen al colapsar (h-8) y son normales al expandir (h-5) - const iconSize = isCollapsed ? 'h-8 w-8' : 'h-5 w-5'; - - - // El sidebar se colapsa a 96px (w-24) y se expande a 256px (w-64). return ( -