diff --git a/diplomas/package-lock.json b/diplomas/package-lock.json index 5709836..7d3d824 100644 --- a/diplomas/package-lock.json +++ b/diplomas/package-lock.json @@ -29,6 +29,7 @@ "react-hook-form": "^7.56.2", "tailwind-merge": "^3.2.0", "tw-animate-css": "^1.2.5", + "xlsx": "^0.18.5", "zod": "^3.24.3" }, "devDependencies": { @@ -2829,6 +2830,14 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3198,6 +3207,18 @@ } ] }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3238,6 +3259,14 @@ "node": ">=6" } }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -3293,6 +3322,17 @@ "node": ">=18" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4195,6 +4235,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -6253,6 +6301,17 @@ "node": ">=0.10.0" } }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -6875,6 +6934,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -6904,6 +6979,26 @@ } } }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/diplomas/package.json b/diplomas/package.json index 1ae39da..f19bb18 100644 --- a/diplomas/package.json +++ b/diplomas/package.json @@ -30,6 +30,7 @@ "react-hook-form": "^7.56.2", "tailwind-merge": "^3.2.0", "tw-animate-css": "^1.2.5", + "xlsx": "^0.18.5", "zod": "^3.24.3" }, "devDependencies": { diff --git a/diplomas/src/pages/alumnosArchivo.jsx b/diplomas/src/pages/alumnosArchivo.jsx index a3d7f59..83cd140 100644 --- a/diplomas/src/pages/alumnosArchivo.jsx +++ b/diplomas/src/pages/alumnosArchivo.jsx @@ -1,41 +1,65 @@ import React, { useState } from "react"; import Papa from "papaparse"; +import * as XLSX from "xlsx"; import Layout from "@/components/layout/Layout"; import { Button } from "@/components/ui/button"; export default function AlumnosArchivo() { const [archivo, setArchivo] = useState(null); - const [datosCSV, setDatosCSV] = useState([]); + const [datos, setDatos] = useState([]); const manejarArchivo = (e) => { const file = e.target.files[0]; - setArchivo(file); + if (file && (file.name.endsWith(".csv") || file.name.endsWith(".xlsx"))) { + setArchivo(file); + } else { + alert("Solo se permiten archivos .csv o .xlsx"); + } }; const manejarSoltar = (e) => { e.preventDefault(); const file = e.dataTransfer.files[0]; - setArchivo(file); + if (file && (file.name.endsWith(".csv") || file.name.endsWith(".xlsx"))) { + setArchivo(file); + } else { + alert("Solo se permiten archivos .csv o .xlsx"); + } }; - const manejarArrastrar = (e) => { - e.preventDefault(); - }; + const manejarArrastrar = (e) => e.preventDefault(); const extraerContenido = () => { if (!archivo) return; - Papa.parse(archivo, { - header: true, - skipEmptyLines: true, - complete: (result) => { - console.log("Contenido CSV:", result.data); - setDatosCSV(result.data); - }, - error: (error) => { - console.error("Error al leer el CSV:", error.message); - }, - }); + const extension = archivo.name.split(".").pop().toLowerCase(); + + if (extension === "csv") { + Papa.parse(archivo, { + header: true, + skipEmptyLines: true, + complete: (result) => { + console.log("Contenido CSV:", result.data); + setDatos(result.data); + }, + error: (error) => { + console.error("Error al leer el CSV:", error.message); + }, + }); + } else if (extension === "xlsx") { + const reader = new FileReader(); + reader.onload = (e) => { + const data = new Uint8Array(e.target.result); + const workbook = XLSX.read(data, { type: "array" }); + const hoja = workbook.SheetNames[0]; + const contenido = XLSX.utils.sheet_to_json(workbook.Sheets[hoja], { + defval: "", + }); + console.log("Contenido XLSX:", contenido); + setDatos(contenido); + }; + reader.readAsArrayBuffer(archivo); + } }; return ( @@ -45,21 +69,26 @@ export default function AlumnosArchivo() { <h1 className="text-xl font-semibold mb-4 text-black"> Nuevo alumno </h1> - <h1 + <label htmlFor="archivo" onDrop={manejarSoltar} onDragOver={manejarArrastrar} className="border-2 border-gray-300 rounded-md p-8 text-gray-600 cursor-pointer w-80 text-center mb-4" > - Arrastra y suelta un archivo o busca un archivo + {archivo ? ( + <span className="text-black font-medium">{archivo.name}</span> + ) : ( + <span>Arrastra y suelta un archivo o haz clic para seleccionarlo</span> + )} <input type="file" id="archivo" - accept=".csv" + accept=".csv, .xlsx" onChange={manejarArchivo} className="hidden" /> - </h1> + </label> + <Button onClick={extraerContenido} className="bg-green-400 hover:bg-green-500 text-white font-bold py-2 px-4 rounded-md" @@ -67,11 +96,11 @@ export default function AlumnosArchivo() { Extraer contenido </Button> - {datosCSV.length > 0 && ( + {datos.length > 0 && ( <div className="mt-6 text-left w-full max-w-md"> <h3 className="font-bold mb-2">Contenido extraído:</h3> <pre className="bg-gray-100 p-2 rounded overflow-x-auto text-sm"> - {JSON.stringify(datosCSV, null, 2)} + {JSON.stringify(datos, null, 2)} </pre> </div> )}