Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -162,5 +162,8 @@ function indiceALetraColumna(indice) {
}

module.exports = {
aplicarFormula
aplicarFormula,
encontrarColumnaVacia,
traducirFormulaEstructurada,
indiceALetraColumna,
};
152 changes: 152 additions & 0 deletions harvester-app/src/backend/casosUso/formulas/filtrarDatos.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
const { HyperFormula } = require('hyperformula');
const { encontrarColumnaVacia } = require('./aplicarFormula.js')

const opciones = {
licenseKey: 'gpl-v3',
};

/**
* Aplica una fórmula estructurada a una hoja de Excel almacenada en localStorage.
* @param {string} nombreFormula - Nombre de la fórmula a aplicar.
* @param {string} formulaEstructurada - Fórmula estructurada a aplicar.
* @param {string|null} tractorSeleccionado - Nombre de la hoja donde se aplicará la fórmula (opcional).
* @returns {Object} Resultado de la aplicación de la fórmula.
*/

function filtrarDatos(filtro, datosExcel, tractorSeleccionado){
if(filtro.length==0){
return { resultados: datosExcel };
}

// Lógica para filtrar los datos según el filtro

const hojas = datosExcel.hojas;

// Si no se especifica hoja, usar la primera disponible
let datos = [];
let tractorUsado = '';

if (tractorSeleccionado && hojas[tractorSeleccionado]) {
datos = hojas[tractorSeleccionado];
tractorUsado = tractorSeleccionado;
} else {
// Tomar la primera hoja disponible
tractorUsado = Object.keys(hojas)[0];
datos = hojas[tractorUsado];
}

// Verificar que datos sea un array
if (!Array.isArray(datos)) {
throw new Error('Formato de datos inválido: se esperaba un array');
}

if (datos.length === 0) {
throw new Error('No hay datos disponibles en la hoja seleccionada');
}

// Obtener encabezados
const encabezados = datos[0];

// Encontrar la primera columna vacía usando método seguro
const indiceColumnaVacio = encontrarColumnaVacia(datos);



const hyperFormulaInstance = HyperFormula.buildFromArray(datos, opciones);
const sheetId = hyperFormulaInstance.getSheetId('Sheet1');

const columnaCondicion = nombreColumnaAIndice(filtro[0].Datos, encabezados);
if(columnaCondicion.error) {
return { error: true, columnaNoEncontrada: columnaCondicion.columnaNoEncontrada };
}

const filas = datos.length;

const condicionFiltro = extraerCondicionFiltro(filtro[0].Datos);

for(let columna = indiceColumnaVacio; columna < indiceColumnaVacio*2+1; columna+=1) {
hyperFormulaInstance.setCellContents({ row: 0, col: columna, sheet: sheetId }, encabezados[columna-indiceColumnaVacio]);
const columnaLetra = numeroAColumnaExcel(columna-indiceColumnaVacio+1);
hyperFormulaInstance.setCellContents({ row: 1, col: columna, sheet: 0 }, `=FILTER(${columnaLetra}2:${columnaLetra}${filas}, ${columnaCondicion}2: ${columnaCondicion}${filas}${condicionFiltro})`);
}

const resultadosFiltrados = [];
for(let fila = 0; fila < filas; fila+=1) {
const resultadoColumna = [];
for(let columna = indiceColumnaVacio; columna < indiceColumnaVacio*2+1; columna+=1) {
const valorCelda = hyperFormulaInstance.getCellValue({ row: fila, col: columna, sheet: sheetId });
if (valorCelda != null && valorCelda != undefined) {
resultadoColumna.push(valorCelda);
}
}
if (resultadoColumna.length > 0) {
resultadosFiltrados.push(resultadoColumna);
}
}

const resultados = {
hojas: {
[tractorSeleccionado]: resultadosFiltrados
}
};


return {
resultados,
};

}


function numeroAColumnaExcel(num) {
let columna = '';
while (num > 0) {
const residuo = (num - 1) % 26;
columna = String.fromCharCode(65 + residuo) + columna;
num = Math.floor((num - 1) / 26);
}
return columna;
}



/**
* @function traducirFormulaEstructurada
* @param {string} formula - Fórmula estructurada a traducir.
* @param {Array} encabezados - Encabezados de la hoja de Excel.
* @param {number} filaActiva - Fila activa para la traducción.
* @returns {string} Fórmula traducida a formato clásico de Excel.
*/

function nombreColumnaAIndice(filtro, encabezados) {
// Buscar el primer match de [@NombreColumna]
const match = filtro.match(/\[@([^\]]+)\]/);
if (!match) {
return { error: true, columnasNoEncontradas: ['No se encontró ninguna columna en el filtro'] };
}
const nombreColumna = match[1];
const columna = encabezados.indexOf(nombreColumna);
if (columna === -1) {
return { error: true, columnaNoEncontrada: [nombreColumna] };
}
return numeroAColumnaExcel(columna + 1); // +1 porque A=1, B=2, etc.
}


/**
* Extrae la condición de una fórmula tipo =FILTER([@Columna]condición)
* @param {string} filtro - Fórmula tipo =FILTER([@Columna]condición)
* @returns {string|null} La condición (por ejemplo, '>1' o '="Muy rápido"'), o null si no se encuentra.
*/
function extraerCondicionFiltro(filtro) {
// Busca la referencia estructurada y lo que sigue después de ella, hasta el paréntesis de cierre
const match = filtro.match(/\[@[^\]]+\]([^)]+)/);
if (!match) return null;
return match[1].trim();
}



module.exports = {
filtrarDatos,
}
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ body,
gap: 10px;
align-items: center;
justify-content: flex-start;
height: 650px;
height: wrap-content;
position: relative;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ input:focus {
flex-shrink: 0;
position: relative;
display: none;
height: 100%;
}
.secundario {
background: #e8e8e8;
Expand Down Expand Up @@ -96,10 +97,26 @@ input:focus {
border: none;
}


.tractores-contenido{
height: 100%; /* Ajusta según lo que necesites */
max-height: 34.5em;
overflow-y: auto;
scrollbar-width: thin; /* Firefox */
scrollbar-color: #999 transparent; /* Firefox */
display: flex;
flex-direction: column;
gap: 12px;
align-items: flex-start;
justify-content: flex-start;
align-self: stretch;
flex-shrink: 0;
position: relative;
}

.distribuidores-contenido,
.tractores-contenido,
.columnas-contenido {
max-height: 300px; /* Ajusta según lo que necesites */
height: 40em; /* Ajusta según lo que necesites */
overflow-y: auto;
scrollbar-width: thin; /* Firefox */
scrollbar-color: #999 transparent; /* Firefox */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ const { eliminarCuadroFormulas } = require('./eliminarCuadroFormulas');
const { mostrarAlerta } = require(`${rutaBase}/src/framework/vistas/includes/componentes/moleculas/alertaSwal/alertaSwal`);
const { filtrarYRenderizarFormulas, actualizarCaracteresBuscador } = require(`${rutaBase}/src/framework/utils/scripts/paginas/analisis/formulas/filtrarYRenderizarFormulas.js`);
const { aplicarFormula } = require(`${rutaBase}/src/backend/casosUso/formulas/aplicarFormula.js`);
const { actualizarGraficaConColumna } = require(`${rutaBase}/src/framework/utils/scripts/paginas/analisis/graficas/actualizarGraficaConColumna.js`);
const { filtrarDatos } = require(`${rutaBase}/src/backend/casosUso/formulas/filtrarDatos.js`);
const { procesarDatosUniversal } = require(`${rutaBase}/src/framework/utils/scripts/paginas/analisis/graficas/procesarDatosUniversal.js`);
const { obtenerParametrosTractor } = require(`${rutaBase}/src/framework/utils/scripts/paginas/analisis/formulas/obtenerParametrosTractor.js`);
const { retirarDatos } = require(`${rutaBase}/src/framework/utils/scripts/paginas/analisis/graficas/retirarDatos.js`);
const { crearGrafica } = require(`${rutaBase}/src/framework/utils/scripts/paginas/analisis/graficas/crearGrafica.js`);
const { crearMenuParametros } = require(`${rutaBase}/src/framework/utils/scripts/paginas/analisis/formulas/crearMenuParametros.js`);
const { crearMenuFiltros } = require(`${rutaBase}/src/framework/utils/scripts/paginas/analisis/formulas/crearMenuFiltros.js`);
const Chart = require('chart.js/auto');

/**
Expand Down Expand Up @@ -35,6 +36,11 @@ async function crearCuadroFormulas(graficaId, formulasDisponibles, datosOriginal
mensajeInicial = 'No hay fórmulas disponibles.';
}

const filtrosDisponibles = formulasDisponibles.filter(formula => {
return formula.Datos.toLowerCase().includes('filter');
});


cuadroFormulas.innerHTML = `<div class='titulo-formulas'>
<img class='flecha-atras' src='${rutaBase}/src/framework/utils/iconos/FlechaAtras.svg' />
<p class='texto'>Fórmulas</p>
Expand All @@ -45,6 +51,11 @@ async function crearCuadroFormulas(graficaId, formulasDisponibles, datosOriginal
<div class='opciones-carta'>
</div>
</div>
<div class='opciones-seccion'>
<p>Filtros</p>
<div class='opciones-carta'>
</div>
</div>
<div class='opciones-seccion'>
<div class='titulo-aplicar-formulas'>
<p>Aplicar Fórmula</p>
Expand All @@ -64,12 +75,14 @@ async function crearCuadroFormulas(graficaId, formulasDisponibles, datosOriginal
</div>
</div>
</div>

</div>`;

const contenedoesSeleccion = cuadroFormulas.querySelectorAll('.opciones-carta');
const contenedoresSeleccion = cuadroFormulas.querySelectorAll('.opciones-carta');

//ToDo: Escalar en número de variables dependiendo de las variables en las fórmulas
crearMenuDesplegable(contenedoesSeleccion[0], 'A', columnasActualizadas, graficaId, datosOriginalesFormulas, tractorSeleccionado);
crearMenuParametros(contenedoresSeleccion[0], columnasActualizadas, graficaId, datosOriginalesFormulas, tractorSeleccionado, filtrosDisponibles, contenedoresSeleccion[1]);
crearMenuFiltros(contenedoresSeleccion[1], filtrosDisponibles, graficaId);

// Configurar búsqueda de fórmulas
const campoBusqueda = cuadroFormulas.querySelector('.search-section');
Expand All @@ -88,6 +101,10 @@ async function crearCuadroFormulas(graficaId, formulasDisponibles, datosOriginal

botonAplicarFormula.addEventListener('click', () => {

const filtroAplicado = filtrosDisponibles.filter(filtro => {
return contenedoresSeleccion[1].querySelector('.opcion-texto').value == filtro.Nombre;
});

const textoAplicar = botonAplicarFormula.querySelector('div');
if (textoAplicar) {
textoAplicar.textContent = 'Aplicando...';
Expand All @@ -96,13 +113,21 @@ async function crearCuadroFormulas(graficaId, formulasDisponibles, datosOriginal
const formulaSeleccionada = contenedorBusqueda.querySelector('.formula-seleccionada');
if (!formulaSeleccionada) {
mostrarAlerta('Error', 'Debes buscar y seleccionar una fórmula antes de aplicar.', 'error');
if (textoAplicar) textoAplicar.textContent = 'Aplicar Fórmula';
return;
}

// Verificar que hay datos disponibles
const datosExcel = localStorage.getItem('datosFiltradosExcel');
if (!datosExcel) {

const datosFiltrados = filtrarDatos(filtroAplicado, JSON.parse(localStorage.getItem('datosFiltradosExcel')), tractorSeleccionado);
if (datosFiltrados.error) {
mostrarAlerta(`Columna no encontrada: ${datosFiltrados.columnaNoEncontrada}`, 'Asegúrate de seleccionar todas las columnas necesarias para aplicar este filtro.', 'error');
if (textoAplicar) textoAplicar.textContent = 'Aplicar Fórmula';
return;
}

if (!datosFiltrados) {
mostrarAlerta('Error', 'No hay datos de Excel cargados. Por favor, carga un archivo Excel primero.', 'error');
if (textoAplicar) textoAplicar.textContent = 'Aplicar Fórmula';
return;
}

Expand All @@ -112,6 +137,7 @@ async function crearCuadroFormulas(graficaId, formulasDisponibles, datosOriginal

if (!inputRadio) {
mostrarAlerta('Error', 'Error al obtener los datos de la fórmula seleccionada.', 'error');
if (textoAplicar) textoAplicar.textContent = 'Aplicar Fórmula';
return;
}

Expand All @@ -122,6 +148,7 @@ async function crearCuadroFormulas(graficaId, formulasDisponibles, datosOriginal
// Verificar que los datos están completos
if (!datosFormula || datosFormula.trim() === '') {
mostrarAlerta('Error', 'Los datos de la fórmula están vacíos o incompletos.', 'error');
if (textoAplicar) textoAplicar.textContent = 'Aplicar Fórmula';
return;
}

Expand All @@ -131,15 +158,16 @@ async function crearCuadroFormulas(graficaId, formulasDisponibles, datosOriginal

if (!graficaDiv) {
mostrarAlerta('Error', 'No se encontró la gráfica asociada.', 'error');
if (textoAplicar) textoAplicar.textContent = 'Aplicar Fórmula';
return;
}

try {
let resultadoFormula;
if (tractorSeleccionado.length != 0) {
resultadoFormula = aplicarFormula(nombreFormula, datosFormula, tractorSeleccionado, JSON.parse(datosExcel));
resultadoFormula = aplicarFormula(nombreFormula, datosFormula, tractorSeleccionado, datosFiltrados.resultados);
} else {
resultadoFormula = aplicarFormula(nombreFormula, datosFormula, null, JSON.parse(datosExcel));
resultadoFormula = aplicarFormula(nombreFormula, datosFormula, null, datosFiltrados.resultados);
}
let contadorErrores = 0;
const resultados = resultadoFormula.resultados;
Expand Down Expand Up @@ -215,7 +243,7 @@ async function crearCuadroFormulas(graficaId, formulasDisponibles, datosOriginal
if (textoAplicar) textoAplicar.textContent = 'Aplicar Fórmula';
}
}, 100);
}
}
});

// Configurar evento de búsqueda (filtrado local)
Expand Down Expand Up @@ -251,51 +279,7 @@ async function crearCuadroFormulas(graficaId, formulasDisponibles, datosOriginal
return datosOriginalesFormulas;
}

/**
* Crea un menú desplegable para seleccionar columnas.
* @param {HTMLDivElement} contenedor - Contenedor donde se agregará el menú desplegable.
* @param {string} letra - Letra identificadora del menú.
* @param {string[]} columnas - Lista de columnas disponibles.
* @param {number} graficaId - ID de la gráfica asociada.
* @returns {void}
*/
function crearMenuDesplegable(contenedor, letra, columnas, graficaId, datosOriginalesFormulas, tractorSeleccionado) {
const nuevoMenu = document.createElement('div');
nuevoMenu.className = 'opcion';
const seleccionValores = document.createElement('select');
seleccionValores.className = 'opcion-texto';
seleccionValores.innerHTML = '<option value="">-- Selecciona una columna --</option>'
columnas.forEach((texto) => {
seleccionValores.innerHTML = `${seleccionValores.innerHTML}
<option value="${texto}"> ${texto} </option>`
});

// Agregar evento de cambio para actualizar la gráfica
seleccionValores.addEventListener('change', (evento) => {
const columnaSeleccionada = evento.target.value;
if (columnaSeleccionada && columnaSeleccionada !== '') {
actualizarGraficaConColumna(graficaId, columnaSeleccionada, datosOriginalesFormulas, tractorSeleccionado);
} else {
// Si se deselecciona, resetear la gráfica a estado inicial
const graficaDiv = document.getElementById(`previsualizacion-grafica-${graficaId}`);
if (graficaDiv) {
const canvas = graficaDiv.querySelector('canvas');
const contexto = canvas.getContext('2d');
const graficaExistente = Chart.getChart(contexto);

if (graficaExistente) {
graficaExistente.destroy();
const nuevaGrafica = crearGrafica(contexto, 'line');
nuevaGrafica.options.plugins.title.text = '';
nuevaGrafica.update();
}
}
}
});

nuevoMenu.appendChild(seleccionValores);
contenedor.appendChild(nuevoMenu);
}

module.exports = {
crearCuadroFormulas
Expand Down
Loading
Loading