From 87f52777c457271be8b40bbd64ea243b0332bc69 Mon Sep 17 00:00:00 2001 From: mgonzalez Date: Tue, 3 Mar 2026 22:54:06 -0500 Subject: [PATCH] feat(ui): migrate to Tailwind UI and add result matrix tooling --- backend/src/routes/admin.js | 61 ++ .../scripts/generate_result_model_template.js | 21 + backend/src/services/resultModelService.js | 89 ++ backend/src/sql/tenant_init.sql | 1 + .../src/sql/tenant_migration_questions.sql | 44 + backend/src/sql/tenant_schema.sql | 11 + desarrollo/METODO_RESULTADOS_CLIMA.md | 80 ++ desarrollo/PARA_NUEVO_CHAT.md | 12 + frontend/package-lock.json | 914 ++++++++++++++++++ frontend/package.json | 3 + frontend/postcss.config.cjs | 6 + frontend/src/App.jsx | 1 - frontend/src/main.jsx | 2 +- frontend/src/pages/AdminDashboard.jsx | 2 +- frontend/src/pages/QuestionBank.jsx | 105 +- frontend/src/pages/SuperadminCompanies.jsx | 2 +- frontend/src/pages/SurveyPeriods.jsx | 2 +- frontend/src/styles/app.css | 257 ----- frontend/src/styles/index.css | 198 ++++ frontend/tailwind.config.cjs | 27 + templates/plantilla_matriz_clima.xlsx | Bin 0 -> 6666 bytes 21 files changed, 1575 insertions(+), 263 deletions(-) create mode 100644 backend/src/scripts/generate_result_model_template.js create mode 100644 backend/src/services/resultModelService.js create mode 100644 desarrollo/METODO_RESULTADOS_CLIMA.md create mode 100644 frontend/postcss.config.cjs delete mode 100644 frontend/src/styles/app.css create mode 100644 frontend/src/styles/index.css create mode 100644 frontend/tailwind.config.cjs create mode 100644 templates/plantilla_matriz_clima.xlsx diff --git a/backend/src/routes/admin.js b/backend/src/routes/admin.js index f5accb5..bb68d31 100644 --- a/backend/src/routes/admin.js +++ b/backend/src/routes/admin.js @@ -18,6 +18,13 @@ const { createQuestionSetEmpty, addQuestionToSet } = require('../services/questionService'); +const { + listResultModel, + createResultModel, + updateResultModel, + exportResultModelTemplate, + importResultModelExcel +} = require('../services/resultModelService'); const upload = multer(); const router = express.Router(); @@ -183,6 +190,60 @@ router.get('/question-sets/:id/questions', async (req, res, next) => { } }); +router.get('/result-model', async (req, res, next) => { + try { + const rows = await listResultModel(req.tenantPool); + res.json(rows); + } catch (err) { + next(err); + } +}); + +router.post('/result-model', async (req, res, next) => { + try { + const { dimension, variable, subvariable, score_value, max_score, sort_order } = req.body; + if (!dimension || !variable || !subvariable) { + return res.status(400).json({ error: 'dimension, variable y subvariable requeridos' }); + } + const result = await createResultModel(req.tenantPool, { dimension, variable, subvariable, score_value, max_score, sort_order }); + res.json(result); + } catch (err) { + next(err); + } +}); + +router.patch('/result-model/:id', async (req, res, next) => { + try { + const { score_value, max_score, sort_order } = req.body; + await updateResultModel(req.tenantPool, req.params.id, { score_value, max_score, sort_order }); + res.json({ ok: true }); + } catch (err) { + next(err); + } +}); + +router.get('/result-model/template', async (req, res, next) => { + try { + const buffer = await exportResultModelTemplate(); + res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + res.setHeader('Content-Disposition', 'attachment; filename=\"clima_result_model.xlsx\"'); + res.send(buffer); + } catch (err) { + next(err); + } +}); + +router.post('/result-model/import', upload.single('file'), async (req, res, next) => { + try { + if (!req.file) return res.status(400).json({ error: 'Archivo requerido' }); + const mode = req.body?.mode || 'replace'; + const result = await importResultModelExcel(req.file.buffer, req.tenantPool, mode); + res.json(result); + } catch (err) { + next(err); + } +}); + router.get('/dimensions', async (req, res, next) => { try { const rows = await listDimensions(req.tenantPool); diff --git a/backend/src/scripts/generate_result_model_template.js b/backend/src/scripts/generate_result_model_template.js new file mode 100644 index 0000000..5b0aadf --- /dev/null +++ b/backend/src/scripts/generate_result_model_template.js @@ -0,0 +1,21 @@ +const path = require('path'); +const ExcelJS = require('exceljs'); + +async function main() { + const workbook = new ExcelJS.Workbook(); + const sheet = workbook.addWorksheet('Resultados'); + sheet.addRow(['dimension', 'variable', 'subvariable', 'max_score', 'sort_order']); + sheet.addRow(['CREDIBILIDAD', 'Comunicación', 'Accesibilidad', 100, 1]); + sheet.addRow(['CREDIBILIDAD', 'Comunicación', 'Información', 100, 2]); + sheet.addRow(['RESPETO', 'Apoyo', 'Desarrollo', 100, 3]); + sheet.addRow(['IMPARCIALIDAD', 'Equidad', 'Ausencia de favoritismo', 100, 4]); + + const outPath = path.resolve(__dirname, '../../../templates/plantilla_matriz_clima.xlsx'); + await workbook.xlsx.writeFile(outPath); + console.log('Template generado:', outPath); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/backend/src/services/resultModelService.js b/backend/src/services/resultModelService.js new file mode 100644 index 0000000..7f8d152 --- /dev/null +++ b/backend/src/services/resultModelService.js @@ -0,0 +1,89 @@ +const ExcelJS = require('exceljs'); + +async function listResultModel(pool) { + const [rows] = await pool.query( + 'SELECT id, dimension, variable, subvariable, score_value, max_score, sort_order FROM climate_result_model ORDER BY sort_order, id' + ); + return rows; +} + +async function createResultModel(pool, data) { + const { dimension, variable, subvariable, score_value, max_score, sort_order } = data; + const [res] = await pool.query( + 'INSERT INTO climate_result_model (dimension, variable, subvariable, score_value, max_score, sort_order) VALUES (?, ?, ?, ?, ?, ?)', + [dimension, variable, subvariable, score_value || null, max_score || null, sort_order || 0] + ); + return { id: res.insertId }; +} + +async function updateResultModel(pool, id, data) { + const { score_value, max_score, sort_order } = data; + await pool.query( + 'UPDATE climate_result_model SET score_value = COALESCE(?, score_value), max_score = COALESCE(?, max_score), sort_order = COALESCE(?, sort_order) WHERE id = ?', + [score_value, max_score, sort_order, id] + ); +} + +async function exportResultModelTemplate() { + const workbook = new ExcelJS.Workbook(); + const sheet = workbook.addWorksheet('Resultados'); + sheet.addRow(['dimension', 'variable', 'subvariable', 'score_value', 'max_score', 'sort_order']); + sheet.addRow(['CREDIBILIDAD', 'Comunicación', 'Accesibilidad', 37.33, '', 1]); + return workbook.xlsx.writeBuffer(); +} + +async function importResultModelExcel(buffer, pool, mode = 'replace') { + const workbook = new ExcelJS.Workbook(); + await workbook.xlsx.load(buffer); + const sheet = workbook.worksheets[0]; + if (!sheet) return { processed: 0, errors: ['No hay hoja en el archivo'] }; + + const headers = {}; + const headerRow = sheet.getRow(1); + headerRow.eachCell((cell, idx) => { + const key = String(cell.value || '').trim().toLowerCase(); + if (key) headers[key] = idx; + }); + + const required = ['dimension', 'variable', 'subvariable']; + for (const r of required) { + if (!headers[r]) return { processed: 0, errors: [`Falta columna ${r}`] }; + } + + if (mode === 'replace') { + await pool.query('DELETE FROM climate_result_model'); + } + + let processed = 0; + const errors = []; + + for (let i = 2; i <= sheet.rowCount; i++) { + const row = sheet.getRow(i); + const dimension = String(row.getCell(headers.dimension).value || '').trim(); + const variable = String(row.getCell(headers.variable).value || '').trim(); + const subvariable = String(row.getCell(headers.subvariable).value || '').trim(); + if (!dimension || !variable || !subvariable) { + errors.push(`Fila ${i}: dimension, variable y subvariable son obligatorios`); + continue; + } + const score_value = headers.score_value ? row.getCell(headers.score_value).value : null; + const max_score = headers.max_score ? row.getCell(headers.max_score).value : null; + const sort_order = headers.sort_order ? row.getCell(headers.sort_order).value : null; + + await pool.query( + 'INSERT INTO climate_result_model (dimension, variable, subvariable, score_value, max_score, sort_order) VALUES (?, ?, ?, ?, ?, ?)', + [dimension, variable, subvariable, score_value || null, max_score || null, sort_order || 0] + ); + processed += 1; + } + + return { processed, errors }; +} + +module.exports = { + listResultModel, + createResultModel, + updateResultModel, + exportResultModelTemplate, + importResultModelExcel +}; diff --git a/backend/src/sql/tenant_init.sql b/backend/src/sql/tenant_init.sql index 2b13e3d..cdcc8eb 100644 --- a/backend/src/sql/tenant_init.sql +++ b/backend/src/sql/tenant_init.sql @@ -1,4 +1,5 @@ SET FOREIGN_KEY_CHECKS=0; SOURCE tenant_schema.sql; SOURCE seed_questions.sql; +SOURCE seed_result_model.sql; SET FOREIGN_KEY_CHECKS=1; diff --git a/backend/src/sql/tenant_migration_questions.sql b/backend/src/sql/tenant_migration_questions.sql index 022ea97..d5e739c 100644 --- a/backend/src/sql/tenant_migration_questions.sql +++ b/backend/src/sql/tenant_migration_questions.sql @@ -75,3 +75,47 @@ UPDATE survey_questions sq JOIN questions q ON q.id = sq.question_id SET sq.ques UPDATE responses r JOIN questions q ON q.id=r.question_id SET r.question_code = q.code WHERE r.question_code IS NULL OR r.question_code=''; -- Add unique constraints if not exists (run separately if needed) + +-- Result model table and seed +CREATE TABLE IF NOT EXISTS climate_result_model ( + id INT AUTO_INCREMENT PRIMARY KEY, + dimension VARCHAR(120) NOT NULL, + variable VARCHAR(120) NOT NULL, + subvariable VARCHAR(160) NOT NULL, + score_value DECIMAL(6,2) NULL, + max_score DECIMAL(6,2) NULL, + sort_order INT NOT NULL DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +INSERT INTO climate_result_model (dimension, variable, subvariable, score_value, max_score, sort_order) +SELECT * FROM ( + SELECT 'CREDIBILIDAD', 'Comunicación', 'Accesibilidad', NULL, 100, 1 UNION ALL + SELECT 'CREDIBILIDAD', 'Comunicación', 'Información', NULL, 100, 2 UNION ALL + SELECT 'CREDIBILIDAD', 'Integridad', 'Confiabilidad', NULL, 100, 3 UNION ALL + SELECT 'CREDIBILIDAD', 'Integridad', 'Honestidad', NULL, 100, 4 UNION ALL + SELECT 'CREDIBILIDAD', 'Liderazgo', 'Coordinación', NULL, 100, 5 UNION ALL + SELECT 'CREDIBILIDAD', 'Liderazgo', 'Delegación', NULL, 100, 6 UNION ALL + SELECT 'CREDIBILIDAD', 'Liderazgo', 'Visión', NULL, 100, 7 UNION ALL + SELECT 'RESPETO', 'Apoyo', 'Desarrollo', NULL, 100, 8 UNION ALL + SELECT 'RESPETO', 'Apoyo', 'Reconocimiento', NULL, 100, 9 UNION ALL + SELECT 'RESPETO', 'Cuidado', 'Entorno de Trabajo', NULL, 100, 10 UNION ALL + SELECT 'RESPETO', 'Cuidado', 'Vida Personal', NULL, 100, 11 UNION ALL + SELECT 'RESPETO', 'Sinergia de Equipo', 'Participación', NULL, 100, 12 UNION ALL + SELECT 'IMPARCIALIDAD', 'Equidad', 'Ausencia de favoritismo', NULL, 100, 13 UNION ALL + SELECT 'IMPARCIALIDAD', 'Equidad', 'Equidad en el trato', NULL, 100, 14 UNION ALL + SELECT 'IMPARCIALIDAD', 'Equidad', 'Equidad en la remuneración', NULL, 100, 15 UNION ALL + SELECT 'IMPARCIALIDAD', 'Justicia', 'Capacidad de apelación', NULL, 100, 16 UNION ALL + SELECT 'IMPARCIALIDAD', 'Justicia', 'Justicia en el trato a las personas', NULL, 100, 17 UNION ALL + SELECT 'CAMARADERIA', 'Fraternidad', 'Compañerismo', NULL, 100, 18 UNION ALL + SELECT 'CAMARADERIA', 'Hospitalidad', 'Hospitalidad de personas', NULL, 100, 19 UNION ALL + SELECT 'CAMARADERIA', 'Hospitalidad', 'Hospitalidad del lugar', NULL, 100, 20 UNION ALL + SELECT 'CAMARADERIA', 'Hospitalidad', 'Sentido de equipo', NULL, 100, 21 UNION ALL + SELECT 'PERTENENCIA', 'Orgullo', 'Orgullo del Equipo', NULL, 100, 22 UNION ALL + SELECT 'PERTENENCIA', 'Orgullo', 'Orgullo del Trabajo', NULL, 100, 23 UNION ALL + SELECT 'PERTENENCIA', 'Orgullo', 'Orgullo por la Institución', NULL, 100, 24 +) AS data +WHERE NOT EXISTS (SELECT 1 FROM climate_result_model); + +UPDATE climate_result_model SET max_score = 100 WHERE max_score IS NULL; +UPDATE climate_result_model SET score_value = NULL; diff --git a/backend/src/sql/tenant_schema.sql b/backend/src/sql/tenant_schema.sql index 74d0638..fd67b8a 100644 --- a/backend/src/sql/tenant_schema.sql +++ b/backend/src/sql/tenant_schema.sql @@ -157,3 +157,14 @@ CREATE TABLE IF NOT EXISTS survey_rules ( rule_json JSON NOT NULL, is_active TINYINT(1) NOT NULL DEFAULT 1 ); + +CREATE TABLE IF NOT EXISTS climate_result_model ( + id INT AUTO_INCREMENT PRIMARY KEY, + dimension VARCHAR(120) NOT NULL, + variable VARCHAR(120) NOT NULL, + subvariable VARCHAR(160) NOT NULL, + score_value DECIMAL(6,2) NULL, + max_score DECIMAL(6,2) NULL, + sort_order INT NOT NULL DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); diff --git a/desarrollo/METODO_RESULTADOS_CLIMA.md b/desarrollo/METODO_RESULTADOS_CLIMA.md new file mode 100644 index 0000000..dae9187 --- /dev/null +++ b/desarrollo/METODO_RESULTADOS_CLIMA.md @@ -0,0 +1,80 @@ +# Método de presentación de resultados (basado en Rankmi) y adaptación al sistema + +Este documento describe **cómo trabajar los resultados** de la encuesta de clima laboral en el sistema, **sin cambiar el contenido de la lista de preguntas actual**. Se toma como guía el enfoque de Rankmi de “7 pasos” y se adapta a nuestro flujo y módulos existentes. citeturn0search0 + +## Principios base + +- La comunicación de resultados debe ir **de lo general a lo específico** y con lenguaje constructivo (enfoque “sándwich”). citeturn0search0 +- Siempre presentar **participación**, **resultados globales**, **ranking de mejores/peores**, y **prioridades de acción**. citeturn0search0 +- Los reportes deben ser **agregados** (sin identificación individual) y con **mínimos de respuestas** si aplica. citeturn0search2turn0search5 + +## 7 pasos (Rankmi) — cómo se aplica en nuestro sistema + +1) **Agradecer** + - Mensaje breve al cerrar la medición. + - En PDF: incluir un párrafo de agradecimiento. + citeturn0search0 + +2) **Recordar objetivos de la encuesta** + - Sección fija en el PDF y en el dashboard (texto corto). + citeturn0search0 + +3) **Informar participación** + - Usar cálculo automático ya implementado (tasa de participación). + - Mostrar en dashboard y PDF. + citeturn0search0 + +4) **Resumen del proceso / histórico** + - Mostrar comparativo con medición anterior (si existe). + - En PDF: bloque “Histórico y acciones previas”. + citeturn0search0 + +5) **Resultados generales** + - Índice global, promedio por dimensión, clasificación (Bajo/Medio/Alto). + - Se explica brevemente cómo se calculó (promedios). + citeturn0search0 + +6) **Mejor y peor evaluado** + - Ranking de dimensiones y/o subvariables. + - En PDF: “Top fortalezas” y “Principales oportunidades”. + citeturn0search0 + +7) **Prioridades / plan de acción** + - En PDF: listado de 3–5 prioridades sugeridas según resultados. + - Deben ser medibles y trazables. + citeturn0search0 + +## Qué NO se cambia + +- **La lista de preguntas actual permanece igual**. +- Solo cambia la forma de **presentar y modelar resultados**. + +## Cómo quedamos en el sistema (Fase 1) + +- Resultados automáticos: + - Promedios por pregunta, dimensión, global. + - Clasificación Bajo/Medio/Alto. + - Tasa de participación. +- Reportes: + - PDF con logo, código de medición, resumen ejecutivo, resultados por dimensión, históricos. + - Excel con crudos + consolidados. + +## Estructura de “Matriz” (modelo base) + +Se usa la tabla `climate_result_model` como plantilla editable: +- **dimension** / **variable** / **subvariable** +- **max_score** (por defecto 100) +- **score_value** se llena solo cuando hay resultados (no visible en matriz de configuración). + +## Recomendación de flujo operativo + +1) Crear medición y activar encuesta. +2) Cerrar medición (se calculan resultados). +3) Revisar dashboard (global, dimensión, histórico). +4) Generar PDF y Excel. +5) Presentar resultados siguiendo los 7 pasos. + +## Nota sobre resultados por área (futuro) + +Rankmi recomienda visualización por área/jerarquía y mínimos de respuestas. +Podemos integrar en Fase 2 (si se requiere). citeturn0search1turn0search5 diff --git a/desarrollo/PARA_NUEVO_CHAT.md b/desarrollo/PARA_NUEVO_CHAT.md index ac8826c..df93d02 100644 --- a/desarrollo/PARA_NUEVO_CHAT.md +++ b/desarrollo/PARA_NUEVO_CHAT.md @@ -51,6 +51,11 @@ Proyecto en `/var/www/react/clima_laboral` con backend Node/Express y frontend R - User: `usuario.prueba@demo.com` / `Usuario2026!` - Question sets en DB `P123456`: `QSET-DEFAULT`, `QSET-20260303-03`, `QSET-20260303-04`, `QSET-20260303-05` (algunos activos/inactivos). +## Credenciales (recordatorio) +- Superadmin: `superadmin@demo.com` / `SuperAdmin2026` +- Admin: `admin.prueba@demo.com` / `Admin2026!` (company_code: `123456`) +- User: `usuario.prueba@demo.com` / `Usuario2026!` (company_code: `123456`) + ## Problemas frecuentes - Si “Versiones de Encuesta” sale vacío, es porque no se entró con `company_code` (usar Superadmin > Empresas > Acceder). - Si `/admin/dimensions` da 404, reiniciar backend para cargar rutas nuevas. @@ -60,6 +65,13 @@ Proyecto en `/var/www/react/clima_laboral` con backend Node/Express y frontend R - Sugerencia de nombre de set corregida para usar máximo existente + 1. - Vista Editar Set agregada con lista de preguntas y edición. - Popup 404 silenciado para `/admin/dimensions`. +- Repo Git creado y primer commit limpio (sin node_modules ni token). + +## Git / Repo +- Gitea: `https://gitea.linkedcorp.net/` +- Repo: `mgonzalez/claboral.linkedcorp.net` +- Rama: `main` +- Remote: `origin` ## Pendientes - A gusto del usuario, agregar etiqueta “Escala 1–5” visible en encuestas/preguntas. diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 910d9ec..d1da04c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,9 +16,25 @@ }, "devDependencies": { "@vitejs/plugin-react": "^4.3.2", + "autoprefixer": "^10.4.19", + "postcss": "^8.4.38", + "tailwindcss": "^3.4.3", "vite": "^5.4.8" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@babel/code-frame": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", @@ -742,6 +758,44 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@remix-run/router": { "version": "1.23.2", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", @@ -1181,12 +1235,77 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, + "node_modules/autoprefixer": { + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001774", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/axios": { "version": "1.13.6", "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", @@ -1211,6 +1330,32 @@ "node": ">=6.0.0" } }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/browserslist": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", @@ -1258,6 +1403,16 @@ "node": ">= 0.4" } }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001775", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001775.tgz", @@ -1279,6 +1434,44 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1291,6 +1484,16 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1298,6 +1501,19 @@ "dev": true, "license": "MIT" }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1325,6 +1541,20 @@ "node": ">=0.4.0" } }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1440,6 +1670,59 @@ "node": ">=6" } }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/follow-redirects": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", @@ -1476,6 +1759,20 @@ "node": ">= 6" } }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1547,6 +1844,19 @@ "node": ">= 0.4" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -1598,6 +1908,78 @@ "node": ">= 0.4" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -1630,6 +2012,26 @@ "node": ">=6" } }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -1661,6 +2063,30 @@ "node": ">= 0.4" } }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -1689,6 +2115,18 @@ "dev": true, "license": "MIT" }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -1715,6 +2153,43 @@ "dev": true, "license": "MIT" }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1722,6 +2197,39 @@ "dev": true, "license": "ISC" }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/postcss": { "version": "8.5.8", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", @@ -1751,12 +2259,167 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -1833,6 +2496,61 @@ "react-dom": ">=16.8" } }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rollup": { "version": "4.59.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", @@ -1878,6 +2596,30 @@ "fsevents": "~2.3.2" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -1907,6 +2649,171 @@ "node": ">=0.10.0" } }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -1938,6 +2845,13 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, "node_modules/vite": { "version": "5.4.21", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", diff --git a/frontend/package.json b/frontend/package.json index 4c6214f..4014090 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,6 +16,9 @@ }, "devDependencies": { "@vitejs/plugin-react": "^4.3.2", + "autoprefixer": "^10.4.19", + "postcss": "^8.4.38", + "tailwindcss": "^3.4.3", "vite": "^5.4.8" } } diff --git a/frontend/postcss.config.cjs b/frontend/postcss.config.cjs new file mode 100644 index 0000000..5cbc2c7 --- /dev/null +++ b/frontend/postcss.config.cjs @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {} + } +}; diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 692fbc2..533cf4c 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -8,7 +8,6 @@ import ImportUsers from './pages/ImportUsers.jsx'; import ParticipantSurvey from './pages/ParticipantSurvey.jsx'; import QuestionBank from './pages/QuestionBank.jsx'; import creatorLogo from './assets/creator-logo.png'; -import './styles/app.css'; export default function App() { const [notice, setNotice] = useState(null); diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 3fe4a20..091bf57 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { createRoot } from 'react-dom/client'; import App from './App.jsx'; -import './styles/app.css'; +import './styles/index.css'; createRoot(document.getElementById('root')).render(); diff --git a/frontend/src/pages/AdminDashboard.jsx b/frontend/src/pages/AdminDashboard.jsx index 3cc9cdd..b1b6b7f 100644 --- a/frontend/src/pages/AdminDashboard.jsx +++ b/frontend/src/pages/AdminDashboard.jsx @@ -9,7 +9,7 @@ export default function AdminDashboard() { }, []); return ( -
+

Índice Global

{data.current ? `${data.current.score} (${data.current.classification})` : 'Sin datos'}

diff --git a/frontend/src/pages/QuestionBank.jsx b/frontend/src/pages/QuestionBank.jsx index a4ed97f..c49f8ab 100644 --- a/frontend/src/pages/QuestionBank.jsx +++ b/frontend/src/pages/QuestionBank.jsx @@ -1,7 +1,9 @@ import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; import api from '../utils/api'; export default function QuestionBank() { + const navigate = useNavigate(); const [questions, setQuestions] = useState([]); const [sets, setSets] = useState([]); const [file, setFile] = useState(null); @@ -16,6 +18,9 @@ export default function QuestionBank() { const [newQuestionText, setNewQuestionText] = useState(''); const [newSetInfo, setNewSetInfo] = useState(null); const companyCode = localStorage.getItem('company_code'); + const [resultModel, setResultModel] = useState([]); + const [matrixFile, setMatrixFile] = useState(null); + const [newMatrixRow, setNewMatrixRow] = useState({ dimension: '', variable: '', subvariable: '', max_score: '', sort_order: '' }); const load = async () => { try { @@ -38,7 +43,6 @@ export default function QuestionBank() { useEffect(() => { if (!companyCode) { - window.dispatchEvent(new CustomEvent('app:notice', { detail: { type: 'error', message: 'Selecciona una empresa antes de entrar a Preguntas.' } })); return; } load(); @@ -95,6 +99,51 @@ export default function QuestionBank() { window.URL.revokeObjectURL(url); }; + const loadResultModel = async () => { + const res = await api.get('/admin/result-model'); + setResultModel(res.data); + }; + + const downloadMatrixTemplate = async () => { + const res = await api.get('/admin/result-model/template', { responseType: 'blob' }); + const url = window.URL.createObjectURL(new Blob([res.data])); + const a = document.createElement('a'); + a.href = url; + a.download = 'clima_result_model.xlsx'; + a.click(); + window.URL.revokeObjectURL(url); + }; + + const importMatrix = async () => { + if (!matrixFile) return; + const form = new FormData(); + form.append('file', matrixFile); + form.append('mode', 'replace'); + const res = await api.post('/admin/result-model/import', form, { headers: { 'Content-Type': 'multipart/form-data' } }); + setMsg(`Matriz: ${res.data.processed} filas. Errores: ${res.data.errors.length}`); + await loadResultModel(); + }; + + const addMatrixRow = async () => { + const { dimension, variable, subvariable } = newMatrixRow; + if (!dimension || !variable || !subvariable) return; + const payload = { + ...newMatrixRow, + max_score: newMatrixRow.max_score === '' ? 100 : newMatrixRow.max_score + }; + await api.post('/admin/result-model', payload); + setNewMatrixRow({ dimension: '', variable: '', subvariable: '', max_score: '', sort_order: '' }); + await loadResultModel(); + }; + + const saveMatrixRow = async (row) => { + await api.patch(`/admin/result-model/${row.id}`, { + max_score: row.max_score === '' ? null : row.max_score, + sort_order: row.sort_order === '' ? null : row.sort_order + }); + await loadResultModel(); + }; + const createDimension = async () => { if (!newDimName) return; const res = await api.post('/admin/dimensions', { name: newDimName }); @@ -116,6 +165,9 @@ export default function QuestionBank() {

Preguntas

Selecciona una empresa para ver las versiones de encuesta.

+
); } @@ -125,6 +177,7 @@ export default function QuestionBank() {
+
)} + {view === 'matrix' && ( +
+

Matriz de Clima Laboral

+
+ + {' '} + setMatrixFile(e.target.files[0])} /> + +
+
+ setNewMatrixRow({ ...newMatrixRow, dimension: e.target.value })} /> + setNewMatrixRow({ ...newMatrixRow, variable: e.target.value })} /> + setNewMatrixRow({ ...newMatrixRow, subvariable: e.target.value })} /> + setNewMatrixRow({ ...newMatrixRow, max_score: e.target.value })} /> + setNewMatrixRow({ ...newMatrixRow, sort_order: e.target.value })} /> + +
+ + + + + + + + {resultModel.map((row) => ( + + + + + + + + + ))} + +
DimensiónVariableSubvariableMáximoOrdenGuardar
{row.dimension}{row.variable}{row.subvariable} setResultModel(resultModel.map(r => r.id === row.id ? { ...r, max_score: e.target.value } : r))} /> setResultModel(resultModel.map(r => r.id === row.id ? { ...r, sort_order: e.target.value } : r))} /> + +
+ {msg &&

{msg}

} +
+ )} + {view === 'editset' && newSetInfo && (

Editar set: {newSetInfo.code}

diff --git a/frontend/src/pages/SuperadminCompanies.jsx b/frontend/src/pages/SuperadminCompanies.jsx index 0f0da16..76a11dc 100644 --- a/frontend/src/pages/SuperadminCompanies.jsx +++ b/frontend/src/pages/SuperadminCompanies.jsx @@ -113,7 +113,7 @@ export default function SuperadminCompanies() {
{view === 'new' && ( -
+

Nueva Empresa

diff --git a/frontend/src/pages/SurveyPeriods.jsx b/frontend/src/pages/SurveyPeriods.jsx index 6ad2ddf..1911ebb 100644 --- a/frontend/src/pages/SurveyPeriods.jsx +++ b/frontend/src/pages/SurveyPeriods.jsx @@ -31,7 +31,7 @@ export default function SurveyPeriods() {
{view === 'new' && ( -
+

Nueva Medición

diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css deleted file mode 100644 index 91b2052..0000000 --- a/frontend/src/styles/app.css +++ /dev/null @@ -1,257 +0,0 @@ -@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap'); - -:root { - --bg: #f7f4ef; - --ink: #1b1b1f; - --accent: #0c6d5c; - --accent-2: #e1b85b; - --card: #ffffff; - --muted: #6c6c74; - --border: #e5e0d8; -} - -* { box-sizing: border-box; } -body { - margin: 0; - font-family: 'Montserrat', sans-serif; - background: radial-gradient(circle at 20% 0%, #fff7e8 0%, #f7f4ef 55%, #f1ede7 100%); - color: var(--ink); -} - -.layout { - min-height: 100vh; -} - -.topbar { - display: flex; - align-items: center; - justify-content: space-between; - padding: 18px 28px; - background: var(--card); - border-bottom: 1px solid var(--border); - position: sticky; - top: 0; -} - -.topbar h1 { - margin: 0; - font-size: 20px; - letter-spacing: 0.5px; -} - -.brand { - display: flex; - align-items: center; - gap: 12px; -} - -.brand img { - height: 36px; - width: auto; - border-radius: 6px; - border: 1px solid var(--border); - background: #fff; -} - -.topbar nav a { - margin-left: 14px; - text-decoration: none; - color: var(--ink); - font-weight: 600; - display: inline-flex; - align-items: center; - justify-content: center; - width: 34px; - height: 34px; - border-radius: 999px; - background: #f0e9db; - border: 1px solid var(--border); -} - -.topbar nav a.active { - background: var(--accent); - color: #fff; - border: none; -} - -.topbar nav button { - margin-left: 14px; - text-decoration: none; - color: var(--ink); - font-weight: 600; - display: inline-flex; - align-items: center; - justify-content: center; - width: 34px; - height: 34px; - border-radius: 999px; - background: #f0e9db; - border: 1px solid var(--border); - cursor: pointer; -} - -.content { - padding: 28px; -} - -.card { - background: var(--card); - border: 1px solid var(--border); - border-radius: 16px; - padding: 20px; - margin-bottom: 18px; - box-shadow: 0 10px 24px rgba(0,0,0,0.04); -} - -.login-image { - display: flex; - align-items: center; - justify-content: center; - min-height: 240px; -} - -.login-image img { - max-width: 320px; - width: 100%; - height: auto; - border-radius: 12px; - border: 1px solid var(--border); - background: #fff; -} - -.grid { - display: grid; - gap: 16px; - grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); -} - -button { - background: var(--accent); - color: #fff; - border: none; - padding: 10px 16px; - border-radius: 10px; - font-weight: 600; - cursor: pointer; -} - -button.secondary { - background: var(--accent-2); - color: #3b2c14; -} - -input, select { - width: 100%; - padding: 10px; - border-radius: 10px; - border: 1px solid var(--border); - margin-top: 6px; - margin-bottom: 12px; -} - -label { font-size: 12px; color: var(--muted); } - -.table { - width: 100%; - border-collapse: collapse; -} - -.table th, .table td { - text-align: left; - padding: 10px; - border-bottom: 1px solid var(--border); -} - -.subnav { - display: flex; - gap: 12px; - padding: 12px 18px; - background: var(--card); - border: 1px solid var(--border); - border-radius: 14px; - margin-bottom: 18px; -} - -.subnav button { - background: #f0e9db; - color: var(--ink); - border: 1px solid var(--border); - width: 40px; - height: 40px; - border-radius: 999px; - display: inline-flex; - align-items: center; - justify-content: center; -} - -.subnav button.active { - background: var(--accent); - color: #fff; - border: none; -} - -.danger { - background: #b23a3a; - color: #fff; -} - -.action-btn { - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 999px; - width: 40px; - height: 40px; - font-weight: 700; - border: none; - box-shadow: 0 8px 16px rgba(0,0,0,0.08); - margin: 4px 4px 4px 0; -} - -.topbar nav a { font-size: 16px; } -.subnav button { font-size: 16px; } -.action-btn { font-size: 16px; } -button, a { font-size: 16px; } - -.action-suspend { background: #e4b85b; color: #2b1a00; } -.action-activate { background: #7ac7a8; color: #0c2b1f; } -.action-save { background: #0c6d5c; color: #fff; } -.action-report { background: #0a5f7a; color: #fff; } -.action-delete { background: #b23a3a; color: #fff; } - -.badge { - padding: 4px 10px; - border-radius: 999px; - font-size: 12px; - background: #e6f2ef; - color: var(--accent); - font-weight: 600; -} - -.modal-backdrop { - position: fixed; - inset: 0; - background: rgba(0,0,0,0.45); - display: flex; - align-items: center; - justify-content: center; - z-index: 9999; -} - -.modal { - background: #fff; - border-radius: 16px; - padding: 20px 24px; - width: min(420px, 90vw); - box-shadow: 0 20px 60px rgba(0,0,0,0.2); - border: 1px solid var(--border); -} - -.modal h3 { - margin: 0 0 8px 0; -} - -.modal p { - margin: 0 0 16px 0; - color: var(--muted); -} diff --git a/frontend/src/styles/index.css b/frontend/src/styles/index.css new file mode 100644 index 0000000..e272357 --- /dev/null +++ b/frontend/src/styles/index.css @@ -0,0 +1,198 @@ +@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Work+Sans:wght@400;500;600&display=swap'); + +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --brand-50: #f4f8ff; + --brand-100: #dfeafe; + --brand-200: #b9d3ff; + --brand-300: #8ab3ff; + --brand-400: #5d91ff; + --brand-500: #3b6cff; + --brand-600: #2c53d3; + --brand-700: #233da3; + --brand-800: #1f2f73; + --brand-900: #1a2552; + --erp-font-family-base: 'Work Sans', sans-serif; + --erp-title-size: 20px; + --erp-title-weight: 600; + --erp-title-style: normal; + --erp-subtitle-size: 14px; + --erp-subtitle-weight: 400; + --erp-subtitle-style: normal; + --erp-field-label-size: 12px; + --erp-field-label-weight: 500; + --erp-field-label-style: normal; + --erp-field-input-size: 12px; + --erp-field-input-weight: 400; + --erp-field-input-style: normal; + --erp-table-header-size: 11px; + --erp-table-header-weight: 600; + --erp-table-header-style: normal; + --erp-table-cell-size: 12px; + --erp-table-cell-weight: 400; + --erp-table-cell-style: normal; + --erp-button-size: 12px; + --erp-button-weight: 600; + --erp-button-style: normal; + --erp-menu-size: 12px; + --erp-menu-weight: 600; + --erp-menu-style: normal; + --erp-card-title-size: 13px; + --erp-card-title-weight: 600; + --erp-card-title-style: normal; + --erp-body-size: 12px; + --erp-body-weight: 400; + --erp-body-style: normal; +} + +body { + font-family: var(--erp-font-family-base); + background: + radial-gradient(circle at top left, rgba(59, 108, 255, 0.2), transparent 40%), + radial-gradient(circle at bottom right, rgba(26, 37, 82, 0.25), transparent 45%), + linear-gradient(120deg, rgba(248, 250, 252, 0.9), rgba(241, 245, 249, 0.85)); +} + +@layer components { + .layout { + @apply min-h-screen; + } + + .topbar { + @apply sticky top-0 z-10 flex items-center justify-between border-b border-slate-200 bg-white/90 px-7 py-4 backdrop-blur; + } + + .brand { + @apply flex items-center gap-3; + } + + .topbar h1 { + font-size: var(--erp-title-size); + font-weight: var(--erp-title-weight); + font-style: var(--erp-title-style); + } + + .topbar nav a, + .topbar nav button { + @apply ml-3 inline-flex h-9 w-9 items-center justify-center rounded-full border border-slate-200 bg-white text-slate-700 shadow-sm transition; + } + + .topbar nav a.active { + @apply bg-brand-500 text-white; + } + + .content { + @apply px-7 py-6; + } + + .card { + @apply mb-4 rounded-2xl border border-slate-200 bg-white/90 p-5 shadow-sm; + } + + .login-image { + @apply flex min-h-[240px] items-center justify-center; + } + + .login-image img { + @apply w-full max-w-sm rounded-xl border border-slate-200 bg-white; + } + + .grid-layout { + @apply grid gap-4 md:grid-cols-2 xl:grid-cols-4; + } + + button { + font-size: var(--erp-button-size); + font-weight: var(--erp-button-weight); + font-style: var(--erp-button-style); + } + + input, + select, + textarea { + font-size: var(--erp-field-input-size); + font-weight: var(--erp-field-input-weight); + font-style: var(--erp-field-input-style); + @apply w-full rounded-xl border border-slate-200 px-3 py-2; + } + + label { + font-size: var(--erp-field-label-size); + font-weight: var(--erp-field-label-weight); + font-style: var(--erp-field-label-style); + @apply text-slate-500; + } + + .table { + @apply w-full border-collapse; + } + + .table th { + font-size: var(--erp-table-header-size); + font-weight: var(--erp-table-header-weight); + font-style: var(--erp-table-header-style); + @apply border-b border-slate-200 px-3 py-2 text-left text-slate-500; + } + + .table td { + font-size: var(--erp-table-cell-size); + font-weight: var(--erp-table-cell-weight); + font-style: var(--erp-table-cell-style); + @apply border-b border-slate-100 px-3 py-2 text-left; + } + + .subnav { + @apply mb-4 flex gap-3 rounded-2xl border border-slate-200 bg-white/90 px-4 py-3 shadow-sm; + } + + .subnav button { + @apply inline-flex h-9 w-9 items-center justify-center rounded-full border border-slate-200 bg-white text-slate-700 shadow-sm; + } + + .subnav button.active { + @apply bg-brand-500 text-white; + } + + .action-btn { + @apply inline-flex h-10 w-10 items-center justify-center rounded-full border border-transparent shadow-sm; + } + + .action-btn i { + font-size: 16px; + } + + .action-suspend { + @apply bg-amber-400 text-amber-950; + } + + .action-activate { + @apply bg-emerald-400 text-emerald-950; + } + + .action-save { + @apply bg-brand-500 text-white; + } + + .action-report { + @apply bg-sky-600 text-white; + } + + .action-delete { + @apply bg-rose-600 text-white; + } + + .badge { + @apply rounded-full bg-blue-50 px-2 py-1 text-xs font-semibold text-blue-700; + } + + .modal-backdrop { + @apply fixed inset-0 z-50 flex items-center justify-center bg-black/40; + } + + .modal { + @apply w-[min(420px,90vw)] rounded-2xl border border-slate-200 bg-white p-6 shadow-xl; + } +} diff --git a/frontend/tailwind.config.cjs b/frontend/tailwind.config.cjs new file mode 100644 index 0000000..ba8cee2 --- /dev/null +++ b/frontend/tailwind.config.cjs @@ -0,0 +1,27 @@ +module.exports = { + content: ['./index.html', './src/**/*.{js,jsx}'], + darkMode: 'class', + theme: { + extend: { + fontFamily: { + display: ['"Space Grotesk"', 'sans-serif'], + body: ['"Work Sans"', 'sans-serif'] + }, + colors: { + brand: { + 50: 'var(--brand-50)', + 100: 'var(--brand-100)', + 200: 'var(--brand-200)', + 300: 'var(--brand-300)', + 400: 'var(--brand-400)', + 500: 'var(--brand-500)', + 600: 'var(--brand-600)', + 700: 'var(--brand-700)', + 800: 'var(--brand-800)', + 900: 'var(--brand-900)' + } + } + } + }, + plugins: [] +}; diff --git a/templates/plantilla_matriz_clima.xlsx b/templates/plantilla_matriz_clima.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..d6e97464aa89025a72ca1db50c22beb62e47fc69 GIT binary patch literal 6666 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 MxA2POB)u~KKgB@!_5c6? literal 0 HcmV?d00001