/**
* Main application script - Entry point for the Building Signal Simulator.
* Initializes the application, sets up event listeners, and manages global state.
* @module script
*/
import { initTheme } from './modules/theme.js';
import { initTabs, switchTab } from './modules/tabs.js';
import { handleFormSubmit as handleInitialConfigFormSubmit } from './modules/forms.js';
import { updateComponentForm } from './modules/forms.js';
import {
fetchConfigurations,
fetchSignalTypes,
fetchComponentsByType,
submitComponent,
runSimulation,
fetchSimulationHistory,
deleteSimulationHistory,
saveSchematicComponent,
loadSchematic,
deleteSchematicComponent,
saveSimulationHistory,
updateConfiguration,
deleteConfiguration,
saveSimulationResults,
loadSimulationResults,
deleteSimulationResults,
optimizeBuilding,
} from './modules/servlet.js';
import { displayError, displaySuccess, clearMessages, formatDate } from './modules/utils.js';
import {
updateConfigSelect,
updateSignalTypeSelect,
updateFloorSelector,
updateDetailedComponentList,
updateSimulationResults,
updateSimulationHistoryTable,
handleEditConfiguration,
} from './modules/ui.js';
import { SchematicEditor } from './modules/schematic.js';
document.addEventListener('DOMContentLoaded', async () => {
// --- Core Initializations ---
initTheme();
initTabs();
// --- DOM Element References ---
const errorMessageElement = document.getElementById('error-message');
const successMessageElement = document.getElementById('success-message');
// Close message handlers
document.querySelectorAll('.message-close').forEach((button) => {
button.addEventListener('click', () => {
const messageElement = button.closest('#error-message, #success-message');
if (messageElement) {
messageElement.classList.add('hidden');
}
});
});
// Configuration Tab Elements
const initialConfigForm = document.getElementById('initial-config-form');
const configSelect = document.getElementById('id_configuraciones');
const configDetailsDiv = document.getElementById('config-details');
const configNameDisplay = document.getElementById('config-name-display');
const configHeadendDisplay = document.getElementById('config-headend-display');
const configFloorsDisplay = document.getElementById('config-floors-display');
const configDateDisplay = document.getElementById('config-date-display');
// Components Tab Elements
const componentForm = document.getElementById('component-form');
const componentTypeSelect = document.getElementById('component-type');
const dynamicFieldsContainer = document.getElementById('dynamic-fields');
const componentListTypeSelect = document.getElementById('component-list-type');
const componentListsContainer = {
coaxial: document.getElementById('coaxial-list'),
derivador: document.getElementById('derivador-list'),
distribuidor: document.getElementById('distribuidor-list'),
toma: document.getElementById('toma-list'),
};
// Simulation Tab Elements
const simulationConfigSelect = document.getElementById('simulation-config');
const signalTypeSelect = document.getElementById('signal-type');
const signalFrequencyInput = document.getElementById('signal-frequency');
const simulationFloorSelect = document.getElementById('simulation-floor');
const cableSelect = document.getElementById('cable-select');
const runSimulationButton = document.getElementById('run-simulation');
const runOptimizationButton = document.getElementById('run-optimization');
const schematicCanvas = document.getElementById('schematic-canvas');
// Results Tab Elements
// --- Global State ---
let currentConfigurations = [];
let schematicEditor;
let componentModels = {
coaxial: [],
derivador: [],
distribuidor: [],
toma: [],
};
// --- Helper Functions ---
/**
* Fetches component models for schematic and UI.
* Loads all component types and updates the cable selector.
* @async
* @returns {Promise<void>}
*/
async function loadComponentModels() {
try {
const types = ['coaxial', 'derivador', 'distribuidor', 'toma'];
const results = await Promise.all(types.map(fetchComponentsByType));
componentModels.coaxial = results[0] || [];
componentModels.derivador = results[1] || [];
componentModels.distribuidor = results[2] || [];
componentModels.toma = results[3] || [];
// Update cable selector in Simulation tab
if (cableSelect) {
cableSelect.innerHTML = componentModels.coaxial
.map((model) => `<option value="${model}">${model}</option>`)
.join('');
}
} catch (error) {
console.error('Error fetching component models:', error);
displayError(
'Error al cargar modelos de componentes.',
errorMessageElement,
successMessageElement,
);
}
}
/**
* Updates UI elements based on the selected configuration.
* Displays configuration details and updates the floor selector.
* @param {number} selectedId - The ID of the selected configuration
*/
function handleConfigurationChange(selectedId) {
const selectedConfig = currentConfigurations.find(
(c) => (c.id_configuraciones || c.id) == selectedId,
);
if (selectedConfig) {
if (configDetailsDiv) {
configNameDisplay.textContent = selectedConfig.nombre || '-';
configHeadendDisplay.textContent = selectedConfig.nivel_cabecera
? `${selectedConfig.nivel_cabecera.toFixed(1)} dBμV`
: '-';
configFloorsDisplay.textContent = selectedConfig.num_pisos || '-';
configDateDisplay.textContent = selectedConfig.fecha_creacion
? formatDate(selectedConfig.fecha_creacion)
: '-';
configDetailsDiv.classList.remove('hidden');
}
if (simulationConfigSelect) {
simulationConfigSelect.value = selectedId;
}
if (simulationFloorSelect && selectedConfig.num_pisos) {
updateFloorSelector(selectedConfig.num_pisos);
simulationFloorSelect.value = '';
schematicEditor?.setCurrentFloor(null);
}
schematicEditor?.clearSchematic();
} else {
if (configDetailsDiv) configDetailsDiv.classList.add('hidden');
if (simulationFloorSelect)
simulationFloorSelect.innerHTML = '<option value="">Seleccione Configuración</option>';
}
}
/**
* Fetches and displays the detailed list for the selected component type.
* @async
* @param {string} type - The component type to display
* @returns {Promise<void>}
*/
async function updateDetailedComponentView(type) {
const listElement = componentListsContainer[type];
if (!listElement) return;
Object.values(componentListsContainer).forEach((list) => list.classList.add('hidden'));
listElement.classList.remove('hidden');
try {
const models = await fetchComponentsByType(type);
updateDetailedComponentList(type, models, listElement.id);
} catch (error) {
console.error(`Error fetching or displaying ${type} components:`, error);
displayError(
`Error al cargar ${type}: ${error.message}`,
errorMessageElement,
successMessageElement,
);
listElement.innerHTML = `<div class="text-zinc-500 dark:text-zinc-400">Error al cargar componentes.</div>`;
}
}
// --- Initial Data Loading & Setup ---
try {
clearMessages(errorMessageElement, successMessageElement);
const [configurations, signalTypes] = await Promise.all([
fetchConfigurations(),
fetchSignalTypes(),
]);
currentConfigurations = configurations;
updateConfigSelect(currentConfigurations, configSelect);
updateConfigSelect(currentConfigurations, simulationConfigSelect);
updateSignalTypeSelect(signalTypes, signalTypeSelect);
await loadComponentModels();
schematicEditor = new SchematicEditor('schematic-canvas', {
cables: componentModels.coaxial,
derivadores: componentModels.derivador,
distribuidores: componentModels.distribuidor,
tomas: componentModels.toma,
});
if (componentModels.coaxial.length > 0 && cableSelect) {
schematicEditor.setCableType(cableSelect.value);
}
if (configSelect && configSelect.value) {
handleConfigurationChange(configSelect.value);
}
if (componentListTypeSelect) {
updateDetailedComponentView(componentListTypeSelect.value);
}
} catch (error) {
console.error('Error during initial data load:', error);
displayError(
'Error crítico al inicializar la aplicación. Por favor, recargue.',
errorMessageElement,
successMessageElement,
);
runSimulationButton?.setAttribute('disabled', 'true');
initialConfigForm?.querySelector('button[type="submit"]')?.setAttribute('disabled', 'true');
componentForm?.querySelector('button[type="submit"]')?.setAttribute('disabled', 'true');
}
// --- Event Listeners ---
// Configuration Tab
if (initialConfigForm) {
initialConfigForm.addEventListener('submit', async (event) => {
await handleInitialConfigFormSubmit(
event,
initialConfigForm,
errorMessageElement,
successMessageElement,
configSelect,
);
try {
currentConfigurations = await fetchConfigurations();
updateConfigSelect(currentConfigurations, configSelect);
updateConfigSelect(currentConfigurations, simulationConfigSelect);
} catch (error) {
console.error('Error refetching configurations after submit:', error);
}
});
}
// Edit and delete configuration buttons
const editConfigButton = document.getElementById('edit-config');
const deleteConfigButton = document.getElementById('delete-config');
if (editConfigButton) {
editConfigButton.addEventListener('click', async () => {
const selectedConfigId = configSelect.value;
if (!selectedConfigId) return;
const selectedConfig = currentConfigurations.find(
(c) => (c.id_configuraciones || c.id) == selectedConfigId,
);
if (!selectedConfig) return;
await handleEditConfiguration(
selectedConfigId,
selectedConfig,
async (configId, formData) => {
const response = await updateConfiguration(selectedConfigId, formData);
displaySuccess(
response.success || 'Configuración actualizada correctamente',
successMessageElement,
errorMessageElement,
);
currentConfigurations = await fetchConfigurations();
updateConfigSelect(currentConfigurations, configSelect);
updateConfigSelect(currentConfigurations, simulationConfigSelect);
handleConfigurationChange(selectedConfigId);
},
);
});
}
if (deleteConfigButton) {
deleteConfigButton.addEventListener('click', async () => {
const selectedConfigId = configSelect.value;
if (!selectedConfigId) return;
const selectedConfig = currentConfigurations.find(
(c) => (c.id_configuraciones || c.id) == selectedConfigId,
);
if (!selectedConfig) return;
try {
const simulations = await fetchSimulationHistory(selectedConfigId);
if (simulations && simulations.length > 0) {
displayError(
'No se puede eliminar esta configuración porque tiene simulaciones asociadas. Por favor, elimine primero las simulaciones.',
errorMessageElement,
successMessageElement,
);
return;
}
if (
!confirm(
`¿Está seguro de que desea eliminar la configuración "${selectedConfig.nombre}"?`,
)
) {
return;
}
const response = await deleteConfiguration(selectedConfigId);
displaySuccess(
response.success || 'Configuración eliminada correctamente',
successMessageElement,
errorMessageElement,
);
currentConfigurations = await fetchConfigurations();
updateConfigSelect(currentConfigurations, configSelect);
updateConfigSelect(currentConfigurations, simulationConfigSelect);
if (configDetailsDiv) configDetailsDiv.classList.add('hidden');
} catch (error) {
console.error('Error deleting configuration:', error);
displayError(
'Error al eliminar la configuración. Por favor, intente de nuevo.',
errorMessageElement,
successMessageElement,
);
}
});
}
if (configSelect) {
configSelect.addEventListener('change', () => {
handleConfigurationChange(configSelect.value);
});
configSelect.addEventListener('configurationChanged', (event) => {
handleConfigurationChange(event.detail.configId);
});
}
// Components Tab
if (componentTypeSelect) {
componentTypeSelect.addEventListener('change', (e) => {
updateComponentForm(e.target.value);
});
}
if (componentForm) {
componentForm.addEventListener('submit', async (e) => {
e.preventDefault();
clearMessages(errorMessageElement, successMessageElement);
const formData = new FormData(e.target);
if (!formData.get('type') || !formData.get('modelo') || !formData.get('costo')) {
displayError(
'Por favor complete los campos básicos (Tipo, Modelo, Costo).',
errorMessageElement,
successMessageElement,
);
return;
}
try {
const result = await submitComponent(formData);
if (result.success) {
displaySuccess(result.success, successMessageElement, errorMessageElement);
e.target.reset();
dynamicFieldsContainer.innerHTML = '';
await loadComponentModels();
if (componentListTypeSelect) {
updateDetailedComponentView(componentListTypeSelect.value);
}
schematicEditor?.updateModels({
cables: componentModels.coaxial,
derivadores: componentModels.derivador,
distribuidores: componentModels.distribuidor,
tomas: componentModels.toma,
});
} else {
displayError(
result.error || 'Error al añadir el componente',
errorMessageElement,
successMessageElement,
);
}
} catch (error) {
console.error('Error submitting component:', error);
displayError(
`Error al añadir el componente: ${error.message || 'Error desconocido'}`,
errorMessageElement,
successMessageElement,
);
}
});
}
if (componentListTypeSelect) {
componentListTypeSelect.addEventListener('change', (event) => {
updateDetailedComponentView(event.target.value);
});
}
// Simulation Tab
if (simulationConfigSelect) {
simulationConfigSelect.addEventListener('change', () => {
const newConfigId = simulationConfigSelect.value;
if (configSelect.value !== newConfigId) {
configSelect.value = newConfigId;
}
handleConfigurationChange(newConfigId);
});
}
if (cableSelect) {
cableSelect.addEventListener('change', (e) => {
schematicEditor?.setCableType(e.target.value);
});
}
if (simulationFloorSelect) {
simulationFloorSelect.addEventListener('change', () => {
const selectedFloor = simulationFloorSelect.value;
schematicEditor?.setCurrentFloor(selectedFloor ? parseInt(selectedFloor) : null);
});
}
if (schematicCanvas) {
document.getElementById('zoom-in')?.addEventListener('click', () => schematicEditor?.zoom(1.2));
document
.getElementById('zoom-out')
?.addEventListener('click', () => schematicEditor?.zoom(1 / 1.2));
document
.getElementById('reset-view')
?.addEventListener('click', () => schematicEditor?.resetView());
}
if (runSimulationButton) {
runSimulationButton.addEventListener('click', async () => {
clearMessages(errorMessageElement, successMessageElement);
if (!schematicEditor) {
displayError(
'El editor de esquemático no está inicializado.',
errorMessageElement,
successMessageElement,
);
return;
}
const selectedConfigId = simulationConfigSelect.value;
const selectedSignalType = signalTypeSelect.value;
const frequency = parseInt(signalFrequencyInput.value);
if (!selectedConfigId || !selectedSignalType) {
displayError(
'Por favor, seleccione una configuración y tipo de señal.',
errorMessageElement,
successMessageElement,
);
return;
}
if (isNaN(frequency) || frequency < 470 || frequency > 694) {
displayError(
'Frecuencia inválida (debe ser entre 470 y 694 MHz).',
errorMessageElement,
successMessageElement,
);
return;
}
const componentsData = schematicEditor.getAllComponents();
if (Object.keys(componentsData.floors).length === 0) {
displayError(
'No se han añadido componentes al diseño en el esquemático.',
errorMessageElement,
successMessageElement,
);
return;
}
try {
const selectedConfigData = currentConfigurations.find(
(c) => (c.id_configuraciones || c.id) == selectedConfigId,
);
if (!selectedConfigData) {
throw new Error('Configuración seleccionada no encontrada localmente.');
}
// Validate component hierarchy for each floor
for (const [floorStr, floorComps] of Object.entries(componentsData.floors)) {
const floorNum = parseInt(floorStr);
const hasTomas =
(Array.isArray(floorComps.tomasLeft) && floorComps.tomasLeft.some((model) => model)) ||
(Array.isArray(floorComps.tomasRight) && floorComps.tomasRight.some((model) => model));
const hasDistribuidor =
Array.isArray(floorComps.distribuidores) &&
floorComps.distribuidores.some((model) => model);
const hasDerivador = floorComps.derivador !== undefined && floorComps.derivador !== null;
if (hasTomas && !hasDistribuidor) {
displayError(
`El piso ${floorNum} tiene tomas pero no tiene distribuidor. Debe añadir un distribuidor antes de añadir tomas.`,
errorMessageElement,
successMessageElement,
);
return;
}
if (hasDistribuidor && !hasDerivador) {
displayError(
`El piso ${floorNum} tiene distribuidor pero no tiene derivador. Debe añadir un derivador antes de añadir distribuidores.`,
errorMessageElement,
successMessageElement,
);
return;
}
if (hasDistribuidor && !hasTomas) {
displayError(
`El piso ${floorNum} tiene distribuidor pero no tiene tomas. Debe añadir tomas cuando hay un distribuidor.`,
errorMessageElement,
successMessageElement,
);
return;
}
if (hasDerivador && !hasDistribuidor && !hasTomas) {
displayError(
`El piso ${floorNum} tiene solo un derivador. Debe añadir al menos un distribuidor o tomas.`,
errorMessageElement,
successMessageElement,
);
return;
}
}
// Prepare API payload
const apiComponents = [];
Object.entries(componentsData.floors).forEach(([floorStr, floorComps]) => {
const floorNum = parseInt(floorStr);
if (floorComps.derivador) {
apiComponents.push({ type: 'derivador', model: floorComps.derivador, floor: floorNum });
}
if (Array.isArray(floorComps.distribuidores)) {
floorComps.distribuidores.forEach((model) => {
if (model)
apiComponents.push({ type: 'distribuidor', model: model, floor: floorNum });
});
}
if (Array.isArray(floorComps.tomasLeft)) {
floorComps.tomasLeft.forEach((model) => {
if (model) apiComponents.push({ type: 'toma', model: model, floor: floorNum });
});
}
if (Array.isArray(floorComps.tomasRight)) {
floorComps.tomasRight.forEach((model) => {
if (model) apiComponents.push({ type: 'toma', model: model, floor: floorNum });
});
}
});
const simulationPayload = {
num_pisos: selectedConfigData.num_pisos,
nivel_cabecera: selectedConfigData.nivel_cabecera,
tipo_senal: selectedSignalType,
frequency: frequency,
selected_cable_model: cableSelect.value,
components: apiComponents,
};
const results = await runSimulation(simulationPayload);
results.nivel_cabecera = selectedConfigData.nivel_cabecera;
// Save simulation history
const simulationHistoryData = {
id_configuraciones: selectedConfigId,
frecuencia: frequency,
tipo_senal: selectedSignalType,
costo_total: results.total_cost,
estado: results.signal_levels.every((f) => f.status === 'ok') ? 'ok' : 'error',
};
const simulationResponse = await saveSimulationHistory(simulationHistoryData);
const idSimulacion = simulationResponse.id;
// Save simulation results
try {
await saveSimulationResults(
idSimulacion,
results.signal_levels.map((level) => ({
floor: level.floor,
level: level.level,
floor_cost: level.floor_cost,
status: level.status,
})),
);
} catch (error) {
console.error('Error saving simulation results:', error);
displayError(
`Error al guardar los resultados: ${error.message || 'Error desconocido'}`,
errorMessageElement,
successMessageElement,
);
}
// Save schematic components
const schematicComponents = [];
Object.entries(componentsData.floors).forEach(([floorStr, floorComps]) => {
const floorNum = parseInt(floorStr);
if (floorComps.derivador) {
schematicComponents.push({
id_simulaciones: idSimulacion,
tipo: 'derivador',
modelo: floorComps.derivador,
piso: floorNum,
posicion_x: 0,
posicion_y: 0,
cable_tipo: cableSelect.value,
});
}
if (Array.isArray(floorComps.distribuidores) && floorComps.distribuidores[1]) {
schematicComponents.push({
id_simulaciones: idSimulacion,
tipo: 'distribuidor',
modelo: floorComps.distribuidores[1],
piso: floorNum,
posicion_x: 100,
posicion_y: 0,
cable_tipo: cableSelect.value,
});
}
if (Array.isArray(floorComps.tomasRight)) {
floorComps.tomasRight.forEach((toma, index) => {
if (toma) {
schematicComponents.push({
id_simulaciones: idSimulacion,
tipo: 'toma',
modelo: toma,
piso: floorNum,
posicion_x: 180,
posicion_y: index * 45,
});
}
});
}
});
for (const component of schematicComponents) {
try {
await saveSchematicComponent(component);
} catch (error) {
console.error('Error saving schematic component:', error);
displayError(
`Error al guardar componente: ${error.message || 'Error desconocido'}`,
errorMessageElement,
successMessageElement,
);
}
}
switchTab('results-tab');
updateSimulationResults(results);
displaySuccess(
'Simulación completada con éxito.',
successMessageElement,
errorMessageElement,
);
} catch (error) {
console.error('Error preparing or running simulation:', error);
displayError(
`Error en simulación: ${error.message || 'Error desconocido'}`,
errorMessageElement,
successMessageElement,
);
}
});
}
if (runOptimizationButton) {
runOptimizationButton.addEventListener('click', async () => {
clearMessages(errorMessageElement, successMessageElement);
const selectedConfigId = simulationConfigSelect.value;
const selectedSignalType = signalTypeSelect.value;
const frequency = parseInt(signalFrequencyInput.value);
const cableModel = cableSelect.value;
if (!selectedConfigId || !selectedSignalType || !cableModel) {
displayError(
'Por favor, seleccione configuración, tipo de señal y cable.',
errorMessageElement,
successMessageElement,
);
return;
}
const submitButton = runOptimizationButton;
const originalText = submitButton.innerHTML;
submitButton.disabled = true;
submitButton.innerHTML = 'Calculando...';
try {
const payload = {
id_configuracion: selectedConfigId,
tipo_senal: selectedSignalType,
frequency: frequency,
cable_model: cableModel,
};
const result = await optimizeBuilding(payload);
if (result.success) {
displaySuccess(
`Optimización completada. Se soportan hasta ${result.floors} pisos. Cargando resultados...`,
successMessageElement,
errorMessageElement,
);
const components = await loadSchematic(result.id);
const resultsData = await loadSimulationResults(result.id);
// Update Schematic
if (schematicEditor) {
schematicEditor.clearSchematic();
// Draw components
for (const component of components) {
const floor = component.piso;
schematicEditor.setCurrentFloor(floor);
if (component.tipo_componente === 'derivador') {
schematicEditor._setComponentValue(floor, 'DE', 0, component.modelo_componente);
} else if (component.tipo_componente === 'distribuidor') {
schematicEditor._setComponentValue(floor, 'DI', 1, component.modelo_componente);
} else if (component.tipo_componente === 'toma') {
const slotIndex = Math.floor(component.posicion_y / 45);
schematicEditor._setSingleTomaModel(
floor,
1,
slotIndex,
component.modelo_componente,
);
}
}
if (components[0]?.cable_tipo) {
schematicEditor.setCableType(components[0].cable_tipo);
cableSelect.value = components[0].cable_tipo;
}
schematicEditor.resetView();
}
// Update Results Tab
const selectedOption =
simulationConfigSelect.options[simulationConfigSelect.selectedIndex];
const configData = JSON.parse(selectedOption.dataset.config);
switchTab('results-tab');
updateSimulationResults({
nivel_cabecera: configData.nivel_cabecera,
signal_levels: resultsData.map((r) => ({
floor: r.piso,
level: r.nivel_senal,
status: r.estado,
floor_cost: r.costo_piso,
})),
total_cost: resultsData.reduce((acc, r) => acc + r.costo_piso, 0),
margins: { min: 45, max: 70 },
});
}
} catch (error) {
displayError(
`Error en la optimización: ${error.message}`,
errorMessageElement,
successMessageElement,
);
} finally {
submitButton.disabled = false;
submitButton.innerHTML = originalText;
}
});
}
// Tab Switching Logic
document.querySelectorAll('.tab-button').forEach((button) => {
button.addEventListener('click', () => {
const tabIdBase = button.id.replace('-mobile', '');
if (tabIdBase === 'simulation-tab') {
requestAnimationFrame(() => {
schematicEditor?.initializeCanvas();
const simulationConfigSelect = document.getElementById('simulation-config');
if (simulationConfigSelect && simulationConfigSelect.value) {
schematicEditor?.resetView();
}
});
} else if (tabIdBase === 'components-tab') {
if (componentListTypeSelect.value) {
updateDetailedComponentView(componentListTypeSelect.value);
}
} else if (tabIdBase === 'config-tab') {
if (configSelect.value) {
handleConfigurationChange(configSelect.value);
}
} else if (tabIdBase === 'history-tab') {
loadSimulationHistory();
}
});
});
/**
* Loads simulation history for all configurations and updates the table.
* @async
* @returns {Promise<void>}
*/
async function loadSimulationHistory() {
try {
const configurations = await fetchConfigurations();
if (!configurations || configurations.length === 0) {
displayError(
'No hay configuraciones disponibles',
errorMessageElement,
successMessageElement,
);
return;
}
const allSimulations = [];
for (const config of configurations) {
try {
const configSimulations = await fetchSimulationHistory(
config.id_configuraciones || config.id,
);
if (configSimulations && configSimulations.length > 0) {
const simulationsWithConfig = configSimulations.map((sim) => ({
...sim,
config_name: config.nombre_edificio || config.nombre,
}));
allSimulations.push(...simulationsWithConfig);
}
} catch (error) {
console.error(
`Error fetching history for config ${config.id_configuraciones || config.id}:`,
error,
);
}
}
allSimulations.sort((a, b) => {
const configCompare = (a.config_name || '').localeCompare(b.config_name || '');
if (configCompare !== 0) return configCompare;
return new Date(b.fecha_creacion) - new Date(a.fecha_creacion);
});
updateSimulationHistoryTable(allSimulations);
} catch (error) {
console.error('Error loading simulation history:', error);
displayError(
'Error al cargar el historial de simulaciones',
errorMessageElement,
successMessageElement,
);
}
}
// Simulation history table event handlers
const simulationHistoryTable = document.getElementById('simulation-history-table');
if (simulationHistoryTable) {
/**
* Loads simulation results and displays them in the results tab.
* @async
* @param {number} simulationId - The simulation ID
* @param {HTMLTableRowElement} simulationRow - The table row element containing simulation data
* @param {boolean} [shouldSwitchTab=false] - Whether to switch to the results tab
* @returns {Promise<void>}
*/
async function loadAndDisplayResults(simulationId, simulationRow, shouldSwitchTab = false) {
const nivel_cabecera = parseFloat(simulationRow.cells[1].textContent);
const total_cost = parseFloat(simulationRow.cells[4].textContent.slice(1));
const minLevel = 45;
const maxLevel = 70;
const results = await loadSimulationResults(simulationId);
if (shouldSwitchTab) {
switchTab('results-tab');
}
updateSimulationResults({
nivel_cabecera: nivel_cabecera,
signal_levels: results.map((r) => ({
floor: r.piso,
level: r.nivel_senal,
status: r.estado,
floor_cost: r.costo_piso,
})),
total_cost: total_cost,
margins: {
min: minLevel,
max: maxLevel,
},
});
displaySuccess(
'Resultados cargados correctamente',
successMessageElement,
errorMessageElement,
);
}
simulationHistoryTable.addEventListener('click', async (event) => {
const target = event.target;
const simulationId = target.dataset.simulationId;
if (!simulationId) return;
if (
target.classList.contains('load-schematic') ||
target.classList.contains('load-results')
) {
try {
const simulationRow = target.closest('tr');
const buildingName = simulationRow.cells[0].textContent;
const frequency = simulationRow.cells[3].textContent.replace(' MHz', '');
const isLoadSchematic = target.classList.contains('load-schematic');
const targetTab = isLoadSchematic ? 'simulation-tab' : 'results-tab';
const matchingConfig = currentConfigurations.find(
(config) => (config.nombre_edificio || config.nombre) === buildingName,
);
if (matchingConfig) {
const configId = matchingConfig.id_configuraciones || matchingConfig.id;
simulationConfigSelect.value = configId;
handleConfigurationChange(configId);
}
const components = await loadSchematic(simulationId);
switchTab(targetTab);
if (frequency) {
signalFrequencyInput.value = frequency;
}
schematicEditor?.clearSchematic();
for (const component of components) {
const floor = component.piso;
const type = component.tipo_componente;
const model = component.modelo_componente;
const posX = component.posicion_x;
const posY = component.posicion_y;
schematicEditor?.setCurrentFloor(floor);
switch (type) {
case 'derivador':
schematicEditor?._setComponentValue(floor, 'DE', 0, model);
break;
case 'distribuidor':
const positionIndex = posX > 0 ? 1 : 0;
schematicEditor?._setComponentValue(floor, 'DI', positionIndex, model);
break;
case 'toma':
const sideIndex = posX > 0 ? 1 : 0;
const slotIndex = Math.floor(posY / 45);
schematicEditor?._setSingleTomaModel(floor, sideIndex, slotIndex, model);
break;
}
}
if (components[0]?.cable_tipo) {
cableSelect.value = components[0].cable_tipo;
schematicEditor?.setCableType(components[0].cable_tipo);
}
await loadAndDisplayResults(simulationId, simulationRow, !isLoadSchematic);
displaySuccess(
isLoadSchematic
? 'Esquemático cargado correctamente'
: 'Resultados cargados correctamente',
successMessageElement,
errorMessageElement,
);
} catch (error) {
console.error('Error loading simulation data:', error);
displayError(
`Error al cargar los datos: ${error.message}`,
errorMessageElement,
successMessageElement,
);
}
} else if (target.classList.contains('delete-simulation')) {
if (
!confirm(
'¿Está seguro de que desea eliminar esta simulación? Esta acción no se puede deshacer.',
)
) {
return;
}
try {
await deleteSimulationResults(simulationId);
await deleteSchematicComponent(simulationId);
await deleteSimulationHistory(simulationId);
await loadSimulationHistory();
displaySuccess(
'Simulación eliminada correctamente',
successMessageElement,
errorMessageElement,
);
} catch (error) {
console.error('Error deleting simulation:', error);
displayError(
'Error al eliminar la simulación',
errorMessageElement,
successMessageElement,
);
}
}
});
}
}); // End DOMContentLoaded