cordenadas a1-a6

This commit is contained in:
2026-03-18 18:14:24 -04:00
16 changed files with 1260 additions and 39 deletions
+789 -32
View File
File diff suppressed because it is too large Load Diff
+7
View File
@@ -21,11 +21,18 @@
"react": "19.2.0",
"react-dom": "19.2.0",
"react-hot-toast": "^2.6.0",
<<<<<<< HEAD
"react-leaflet": "^5.0.0"
=======
"react-leaflet": "^5.0.0",
"react-select": "^5.10.2",
"recharts": "^3.8.0"
>>>>>>> Lino
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@types/js-cookie": "^3.0.6",
"@types/leaflet": "^1.9.21",
"@types/node": "^24",
"@types/react": "^19",
"@types/react-dom": "^19",
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

@@ -0,0 +1,10 @@
import "../../styles/layout/reporte.scss";
export default function Page() {
return (
<div className="scanView_reporte">
<div className="container_reporte">
</div>
</div>
);
}
+3 -1
View File
@@ -1,13 +1,15 @@
"use client";
import { useState } from "react";
import Pregunta7 from "@/components/Equipo_Computo/Pregunta7";
import Pregunta1EP from "@/components/Perifericos/Pregunta1EP";
import Pregunta2EP from "@/components/Perifericos/Pregunta2EP";
import Pregunta4EP from "@/components/Perifericos/Pregunta4EP";
import Pregunta5EP from "@/components/Perifericos/Pregunta5EP";
import "../../styles/layout/reporte.scss";
import DownloadReporteXLSX from "@/components/Dowload/Reporte";
import Pregunta7 from "@/components/Equipo_Computo/Pregunta7";
type PreguntaKey = "pregunta1" | "pregunta4" | "pregunta7";
@@ -0,0 +1,61 @@
"use client";
import { useState } from "react";
import DownloadReporteXLSX from "@/components/Dowload/Reporte";
import Reporte from "@/components/reporteGraficas/reporte";
import Antiguedad from "@/components/AntiguedadEquipo/Antiguedad";
import "../../styles/layout/reporte.scss";
type PreguntaKey = "antiguedad" | "adscripcion";
const LABELS: Record<PreguntaKey, string> = {
antiguedad: "Antigüedad",
adscripcion: "Antigüedad por Adscripcion"
};
export default function Page() {
const [activeTab, setActiveTab] = useState<PreguntaKey>("antiguedad");
const renderPregunta = () => {
switch (activeTab) {
case "antiguedad":
return (
<Antiguedad />
);
case "adscripcion":
return (
<Reporte />
);
default:
return (
<div className="p-6 text-center text-gray-500">
Contenido no disponible aún.
</div>
);
}
};
return (
<div className="scanView_reporte">
<div className="container_reporte">
<div className="main-content_reporte">
<div className="tabs_reporte">
{Object.entries(LABELS).map(([key, label]) => (
<button
key={key}
className={`tab_reporte ${activeTab === key ? "active" : ""}`}
onClick={() => setActiveTab(key as PreguntaKey)}
aria-selected={activeTab === key}
role="tab"
>
{label}
</button>
))}
</div>
<div className={`data-table_reporte`}>{renderPregunta()}</div>
</div>
</div>
</div>
);
}
@@ -0,0 +1,43 @@
'use client'
import axios from "axios";
import { useEffect, useState } from "react";
import { Bar, BarChart, CartesianGrid, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
interface antiguedad{
antiguedad:string;
total:number;
}
export default function Antiguedad() {
const [antiguedad,setAntiguedad]=useState<antiguedad[]>([])
useEffect(()=>{
const getAntiguedad = async()=>{
const response = await axios.get("http://localhost:3001/equipos/grafica")
setAntiguedad(response.data)
}
getAntiguedad()
},[])
return (
<ResponsiveContainer width="100%" aspect={2}>
<BarChart data={antiguedad}
width={500}
height={300}
margin={{
top:20,
}}
>
<CartesianGrid strokeDasharray="3 3"/>
<XAxis dataKey="antiguedad"/>
<YAxis/>
<Tooltip/>
<Legend/>
<Bar dataKey="total" fill="#0d6efd"/>
</BarChart>
</ResponsiveContainer>
)
}
@@ -0,0 +1,18 @@
export default function FormEquipo() {
return (
<form>
<label >Selecciona la antiguedad</label>
<select>
<option value=""></option>
</select>
<label>Selecciona la adscripcion</label>
<select>
<option value=""></option>
</select>
<label >Selecciona el edificio</label>
<select>
<option value=""></option>
</select>
</form>
)
}
+5
View File
@@ -49,6 +49,11 @@ function BarNavigation() {
<span>Perifericos</span>
</Link>
</li>
<li className="subMenu" onClick={toggleMenu}>
<Link href="/reportesGraficas" className="links">
<span>Reportes y Graficas</span>
</Link>
</li>
<li className="subMenu">
<button className="logout-button" onClick={cerrarSesion}>
Cerrar sesión
+141
View File
@@ -0,0 +1,141 @@
"use client";
import { MapContainer, ImageOverlay, useMap } from "react-leaflet";
import L from "leaflet";
import "leaflet/dist/leaflet.css";
import "leaflet.heat";
import { useEffect } from "react";
// 🔷 Ajustar vista automáticamente
function AjustarVista({ bounds }) {
const map = useMap();
useEffect(() => {
map.fitBounds(bounds);
}, [map, bounds]);
return null;
}
function HeatLayer() {
const map = useMap();
useEffect(() => {
// ESCALADOS A 4768x3368
const edificios = {
A1: [[1400, 3060], [1470, 3060], [1470, 3360], [1400, 3360]],
A2: [[1570, 3130], [1570, 3190], [1850, 3190], [1850, 3130]],
A3: [[1640, 2650], [1705, 2650], [1705, 2950], [1640, 2950]],
A4: [[1235, 2260], [1235, 2360], [1610, 2360], [1610, 2260]],
A5: [[2250, 2480], [2580, 2480], [2580, 2580], [2250, 2580]],
A6: [[1390, 2385], [1480, 2385], [1480, 2655], [1390, 2655]],
A7: [[2080, 2080], [2250, 2080], [2250, 2480], [2080, 2480]],
A8: [[1820, 2280], [2100, 2280], [2100, 2380], [1820, 2380]],
A9: [[1950, 1880], [2250, 1880], [2250, 1980], [1950, 1980]],
A10: [[1730, 1780], [1900, 1780], [1900, 2130], [1730, 2130]],
A11: [[1700, 1580], [2050, 1580], [2050, 1710], [1700, 1710]],
A12: [[2050, 1280], [2250, 1280], [2250, 1780], [2050, 1780]],
A13: [[1530, 680], [1700, 680], [1700, 1050], [1530, 1050]],
A14: [[1700, 630], [2050, 630], [2050, 750], [1700, 750]],
A15: [[2027, 2580], [2087, 2580], [2087, 2900], [2027, 2900]],
CEDTEC: [[900, 2900], [1100, 2900], [1100, 3200], [900, 3200]],
BIBLIOTECA: [[1200, 1600], [1500, 1600], [1500, 1900], [1200, 1900]],
POSGRADO: [[1100, 1525], [860, 1525], [860, 1325], [1100, 1325]]
};
const puntos = [];
Object.entries(edificios).forEach(([nombre, poligono]) => {
for (let i = 0; i < 15; i++) {
const x = poligono[0][0] + Math.random() * (poligono[1][0] - poligono[0][0]);
const y = poligono[0][1] + Math.random() * (poligono[2][1] - poligono[0][1]);
const intensidad = Math.random();
puntos.push([x, y, intensidad]);
}
});
const heat = L.heatLayer(puntos, {
radius: 35,
blur: 25,
maxZoom: 2,
gradient: {
0.2: "#00f",
0.4: "#0ff",
0.6: "#0f0",
0.8: "#ff0",
1.0: "#f00"
}
});
heat.addTo(map);
Object.entries(edificios).forEach(([nombre, coords]) => {
L.polygon(coords, {
color: "#77ee9f",
weight: 2,
fillOpacity: 0.25
})
.bindPopup(`🏢 ${nombre}`)
.addTo(map);
});
// 🔥 DEBUG PRO (CLICK PARA COORDENADAS EXACTAS)
map.on("click", function (e) {
console.log("📍 Coordenadas exactas:", e.latlng);
});
return () => {
map.removeLayer(heat);
};
}, [map]);
return null;
}
export default function MapaCalor() {
const bounds = [
[0, 0],
[3368, 4768] // 🔥 DIMENSIÓN REAL
];
return (
<div style={{
width: "100%",
height: "650px",
marginTop: "40px",
borderRadius: "12px",
overflow: "hidden",
boxShadow: "0 6px 25px rgba(0,0,0,0.3)"
}}>
<MapContainer
crs={L.CRS.Simple}
minZoom={-2}
style={{ height: "100%", width: "100%" }}
>
<ImageOverlay
url="/plano.png"
bounds={bounds}
/>
<AjustarVista bounds={bounds} />
<HeatLayer />
</MapContainer>
</div>
);
}
+6 -4
View File
@@ -6,6 +6,7 @@ import Cookies from "js-cookie";
import axios from "axios";
import "./pregunta7.css";
import ToggleButton from "../Toggle/ToggleButton";
import MapaCalor from "./MapaCalor";
type AntiguedadItem = {
antiguedad: string | null;
@@ -41,19 +42,19 @@ export default function Pregunta7() {
const antig = item.antiguedad?.toLowerCase().trim();
switch (antig) {
case "Menor a 2 años":
case "menor a 2 años":
menor2 += total;
break;
case "Entre 2 y 3 años":
case "entre 2 y 3 años":
entre2_3 += total;
break;
case "Entre 4 y 5 años":
case "entre 4 y 5 años":
entre4_5 += total;
break;
case "Mayor a 6 años":
case "mayor a 6 años":
mayor6 += total;
break;
}
@@ -167,6 +168,7 @@ export default function Pregunta7() {
</tbody>
</table>
</div>
<MapaCalor />
</div>
+1 -1
View File
@@ -18,7 +18,7 @@ function Header() {
"/ranking",
];
const privateNav = ["/equipoComputo", "/crearCuenta", "/perifericos"];
const privateNav = ["/equipoComputo", "/crearCuenta", "/perifericos","/reportesGraficas"];
return (
<header className={header.header}>
@@ -0,0 +1,76 @@
.container{
width: 100%;
margin: 0 auto;
padding:20px;
background:white;
border-radius:10px;
box-shadow:0 2px 8px rgba(0,0,0,0.08);
font-family:Arial, Helvetica, sans-serif;
font-size:14px;
}
.title{
font-size:18px;
margin-bottom:15px;
}
.label{
font-size:13px;
color:#444;
}
.listaContainer{
margin-top:20px;
}
.subtitle{
font-size:14px;
margin-bottom:10px;
}
.empty{
font-size:13px;
color:#777;
}
.lista{
display:flex;
flex-wrap:wrap;
gap:8px;
}
.chip{
display:flex;
align-items:center;
background:#f1f3f5;
border-radius:20px;
padding:5px 10px;
font-size:13px;
}
.remove{
border:none;
background:none;
margin-left:6px;
cursor:pointer;
color:#777;
}
.remove:hover{
color:#c0392b;
}
.button{
margin-top:20px;
padding:8px 14px;
border:none;
border-radius:6px;
background:#2563eb;
color:white;
font-size:13px;
cursor:pointer;
}
.button:hover{
background:#1d4ed8;
}
@@ -0,0 +1,99 @@
"use client"
import Select from "react-select"
import { useState } from "react"
import styles from "./Reporte.module.css"
type AdscripcionOption = {
value: string
label: string
}
export default function Reporte() {
const [adscripcionesSeleccionadas, setAdscripcionesSeleccionadas] = useState<AdscripcionOption[]>([])
const adscripciones: AdscripcionOption[] = [
{ value: "informatica", label: "Informática" },
{ value: "rh", label: "Recursos Humanos" },
{ value: "finanzas", label: "Finanzas" },
{ value: "juridico", label: "Jurídico" }
]
const agregarAdscripcion = (option: AdscripcionOption | null) => {
if (!option) return
const existe = adscripcionesSeleccionadas.some(a => a.value === option.value)
if (!existe) {
setAdscripcionesSeleccionadas([...adscripcionesSeleccionadas, option])
}
}
const eliminarAdscripcion = (value: string) => {
setAdscripcionesSeleccionadas(
adscripcionesSeleccionadas.filter(a => a.value !== value)
)
}
const generarReporte = () => {
console.log("Adscripciones seleccionadas:", adscripcionesSeleccionadas)
}
return (
<div className={styles.container}>
<h2 className={styles.title}>Reporte de Antigüedad de Equipos</h2>
<label className={styles.label}>Buscar adscripción</label>
<Select
options={adscripciones}
placeholder="Escribe para buscar..."
onChange={(value) => agregarAdscripcion(value)}
/>
<div className={styles.listaContainer}>
<h4 className={styles.subtitle}>
Adscripciones seleccionadas ({adscripcionesSeleccionadas.length})
</h4>
{adscripcionesSeleccionadas.length === 0 && (
<p className={styles.empty}>No hay adscripciones seleccionadas</p>
)}
<div className={styles.lista}>
{adscripcionesSeleccionadas.map((ads) => (
<div key={ads.value} className={styles.chip}>
<span>{ads.label}</span>
<button
className={styles.remove}
onClick={() => eliminarAdscripcion(ads.value)}
>
</button>
</div>
))}
</div>
</div>
<button
className={styles.button}
onClick={generarReporte}
>
Generar reporte
</button>
</div>
)
}
+1 -1
View File
@@ -3,7 +3,7 @@ import type { NextRequest } from "next/server";
import { jwtDecode } from "jwt-decode";
const permisos: Record<number, string[]> = {
1: ["/crearCuenta", "/equipoComputo", "/perifericos","/escaner", "/agregarEquipo", "/editar","/historial","/ranking","/cambiarPass"],
1: ["/crearCuenta", "/equipoComputo", "/perifericos","/escaner", "/hotmap", "/agregarEquipo", "/editar","/historial","/ranking","/cambiarPass","/reportesGraficas"],
2: ["/escaner", "/agregarEquipo", "/editar","/historial","/ranking","/cambiarPass"],
};