Tengo un componente Angular donde abro pestañas que abren un visor de cuadrícula (grid viewer). Este componente carga un JSON y, según ese JSON, inserta un componente en cada celda de la cuadrícula. El problema es que, aunque los componentes parecen cargarse en las celdas, se quedan en blanco hasta que interactúo con la aplicación, como al hacer hover en otro botón de la misma web o al hacer clic en cualquier sitio. Si no hago nada, los componentes no terminan de aparecer. ¿Cómo puedo solucionar este problema?

import { CommonModule } from "@angular/common";
import { HttpClient } from "@angular/common/http";
import {
  AfterViewInit,
  ChangeDetectorRef,
  Component,
  ElementRef,
  HostListener,
  inject,
  Input,
  OnInit,
  QueryList,
  ViewChild,
  ViewChildren,
  ViewContainerRef,
  ViewEncapsulation,
} from "@angular/core";
import { FormsModule } from "@angular/forms";
import { NgxSpinnerModule, NgxSpinnerService } from "ngx-spinner";
import { ToastrService } from "ngx-toastr";

import * as ace from "ace-builds";
import "ace-builds/src-noconflict/mode-json";
import "ace-builds/src-noconflict/theme-dracula";

import { Inject } from "@angular/core";
import { MatDialog } from "@angular/material/dialog";
import { MatTooltipModule } from "@angular/material/tooltip";
import { NgSelectModule } from "@ng-select/ng-select";
import { TranslateService } from "@ngx-translate/core";
import { ComponentContainer } from "golden-layout";
import { TabsComponent } from "src/app/pages/tabs/tabs.component";
import { KpiService } from "src/services/kpi.service";
import { DEFAULT_FILTERS, Filters } from "src/app/models/filter.model";
import { KpiPivotgridComponent } from "../tabs/kpis/kpi-pivotgrid/kpi-pivotgrid.component";
import { GridFilter } from "src/app/models/grid-viewer.model";
import { GridFilterService } from "src/services/grid-filter.service";
import { debounceTime } from "rxjs";

@Component({
  selector: "app-grid-viewer",
  templateUrl: "./grid-viewer.component.html",
  styleUrls: ["./grid-viewer.component.scss"],
  encapsulation: ViewEncapsulation.None
})
export class GridViewerComponent implements OnInit, AfterViewInit {
  @ViewChildren("dynamicComponentContainer", { read: ViewContainerRef })
  dynamicComponentContainers!: QueryList<ViewContainerRef>;
  @ViewChild("contextMenu") contextMenuElement!: ElementRef;
  private kpiService = inject(KpiService);

  gridData: any[] = [];
  gridDataString: string = "";
  gridFiltersDataString: string = "";
  isHidden = false;
  jsonValid: boolean | null = null;
  jsonFiltersValid: boolean | null = null;
  private editor: ace.Ace.Editor | null = null;
  private editorFilters: ace.Ace.Editor | null = null;
  editingCell: any = null;
  contextMenuVisible = false;
  contextMenuX = 0;
  contextMenuY = 0;
  selectedCell: any = null;
  componentOptions: { label: string; value: any }[] = [];
  activeCellInfo: {
    rowIndex: number;
    colIndex: number;
    col: any;
    info: any;
  } | null = null;
  expandedCell: { rowIndex: number; colIndex: number } | null = null;
  availableThemes = [
    { value: "default", label: "Básico", color: "#9cdc82" },
    { value: "custommsi", label: "MSI", color: "#3f3f86" },
    { value: "vintage", label: "Vintage", color: "#c2994f" },
    { value: "macarons", label: "Pastel", color: "#28c5c5" },
    { value: "wonderland", label: "Esmeralda", color: "#55b3a6" },
    { value: "essos", label: "Essos", color: "#ee6058" },
    { value: "westeros", label: "Poniente", color: "#c14089" },
    { value: "dark", label: "Oscuro", color: "#000000" },
  ];

  selectedTheme = this.availableThemes[0];
  customThemes: string[] = ["custommsi", "dark", "vintage", "essos"];
  activeCustomTheme: string | null = null;
  showCalendar = false;
  currentYear = new Date().getFullYear();
  startDate = `${this.currentYear}-01-01`;
  endDate = `${this.currentYear}-12-31`;
  public filters: Filters = DEFAULT_FILTERS;

  // NEW FILTERS FOR SIMMIX 360
  newFilters: GridFilter;
  @Input() isActiveTab: boolean = false;

  // Arreglo para almacenar referencias a los componentes dinámicos creados
  private dynamicComponents: {
    componentRef: any;
    rowIndex: number;
    colIndex: number;
  }[] = [];

  constructor(
    private http: HttpClient,
    private toastr: ToastrService,
    private spinner: NgxSpinnerService,
    private changeDetectorRef: ChangeDetectorRef,
    private dialog: MatDialog,
    private translateService: TranslateService,
    @Inject("Container") public container: ComponentContainer,
    private tabsComponent: TabsComponent,
    private filterService: GridFilterService

  ) {
    filterService.applyFilter$.pipe(debounceTime(500)).subscribe((_) => {
      if (this.isActiveTab) {
        this.newFilters = filterService.getFilters();
      }
    });
  }

  ngOnInit(): void {
    const title = this.translateService.instant("DASHBOARD.TABTITLE");
    this.tabsComponent.setAttributeComponent(
      this["container"]["_componentType"],
      "title",
      title
    );
    this.container.setTitle(title);
    this.getListKPI();
    this.http.get("assets/grid.json").subscribe((data: any) => {
      this.gridData = this.initializeGridData(data);
      this.gridDataString = JSON.stringify(this.gridData, null, 2);
      this.configToastr();
    });
  }

  ngAfterViewInit(): void {
    setTimeout(() => this.renderDynamicComponents(), 100);
  }

  ngOnDestroy() {
    this.hideInfoPanel();
    if (this.expandedCell) {
      const expandedCol =
        this.gridData[this.expandedCell.rowIndex].columns[
        this.expandedCell.colIndex
        ];
      expandedCol.isExpanded = false;
      this.expandedCell = null;
    }
  }

  onThemeChange() {
    console.log(
      "🔰 ~ onThemeChange ~ this.selectedTheme.value:",
      this.selectedTheme.value
    );

    // Actualizar el tema en todos los componentes dinámicos
    this.dynamicComponents.forEach((item) => {
      (item.componentRef.instance as any).theme = this.selectedTheme.value;
    });

    // Actualizar el tema personalizado activo
    this.activeCustomTheme = this.customThemes.includes(
      this.selectedTheme.value
    )
      ? this.selectedTheme.value
      : null;
  }

  getDynamicClasses(col: any): any {
    const classes: any = {
      expanded: col.isExpanded,
      expanding: col.isExpanding,
      contracting: col.isContracting,
    };

    if (this.activeCustomTheme) {
      classes[this.activeCustomTheme] = true;
    }

    return classes;
  }

  getListKPI(): void {
    this.kpiService.getListKPI().subscribe(
      (data: any[]) => {
        this.componentOptions = data.map((item) => ({
          label: `kpi-${item.idKPI} - ${item.representacion} - ${item.nombre}`,
          value: item.idKPI,
        }));
        this.componentOptions.push({
          label: `kpi-1001 - img - test_image`,
          value: 1001,
        });
        this.componentOptions.push({
          label: `kpi-1002 - cardobj - test_cardobj`,
          value: 1002,
        });
        this.componentOptions.push({
          label: `kpi-1003 - line - test_line`,
          value: 1003,
        });
        this.componentOptions.push({
          label: `kpi-1004 - html - test_html`,
          value: 1004,
        });
      },
      (error: any) => {
        // Manejo de errores
      }
    );
  }

  private getActiveKpiNumber(col: any): number {
    if (col.multiSelection && col.showSwitch) {
      return col.switchActiveSide === "left"
        ? col.selectedContent?.value
        : col.alternativeSelectedContent?.value;
    } else if (col.multiSelection) {
      return col.selectedContent?.value;
    } else if (col.showSwitch) {
      return col.switchActiveSide === "left"
        ? col.selectedContent?.value
        : col.alternativeSelectedContent?.value;
    } else {
      return col.selectedContent?.value;
    }
  }

  getLabelTitle(label: string): string {
    const parts = label.split(" - ");
    return parts[parts.length - 1];
  }

  onMultiselectionListChange(e: any, col: any) {
    if (col.multiSelection && col.showSwitch) {
      this.onMultiselectionListChangeWithSwitch(e, col);
    } else {
      col.selectedContent = e;
      col.activeSelectedContent = col.selectedContent;

      const { rowIndex, colIndex } = this.getCellIndices(col);
      this.renderDynamicComponents(rowIndex, colIndex);
    }
  }

  onMultiselectionListChangeWithSwitch(e: any, col: any) {
    const index = col.activeContentOptions.findIndex(
      (option: any) => option.value === e.value
    );

    col.selectedContentIndex = index;

    if (col.switchActiveSide === "left") {
      col.selectedContent = col.contentOptions[index];
      col.alternativeSelectedContent =
        col.alternativeContentOptions[index] ||
        col.alternativeContentOptions[0];

      col.activeSelectedContent = col.selectedContent;
    } else {
      col.alternativeSelectedContent = col.alternativeContentOptions[index];
      col.selectedContent = col.contentOptions[index] || col.contentOptions[0];

      col.activeSelectedContent = col.alternativeSelectedContent;
    }

    const { rowIndex, colIndex } = this.getCellIndices(col);
    this.renderDynamicComponents(rowIndex, colIndex);
  }

  getCellIndices(cell: any): { rowIndex: number; colIndex: number } {
    for (let i = 0; i < this.gridData.length; i++) {
      const row = this.gridData[i];
      const columns = row.columns;
      for (let j = 0; j < columns.length; j++) {
        if (columns[j] === cell) {
          return { rowIndex: i, colIndex: j };
        }
      }
    }

    return { rowIndex: -1, colIndex: -1 }; // Si no se encuentra
  }

  private initializeGridData(data: any[]): any[] {
    return data.map((row) => ({
      ...row,
      columns: row.columns.map((col: any) => ({
        ...col,
        buttons: col.buttons || [],
        content: Array.isArray(col.content)
          ? col.content
          : col.content
            ? [col.content]
            : [],
        alternativeContent: Array.isArray(col.alternativeContent)
          ? col.alternativeContent
          : col.alternativeContent
            ? [col.alternativeContent]
            : [],
        showSwitch: col.showSwitch ?? false,
        switchLeftText: col.switchLeftText || "Izquierda",
        switchRightText: col.switchRightText || "Derecha",
        switchActiveSide: col.switchActiveSide || "left",
        multiSelection: col.multiSelection ?? false,
        contentOptions: Array.isArray(col.contentOptions) ? col.contentOptions : [],
        alternativeContentOptions: Array.isArray(col.alternativeContentOptions) ? col.alternativeContentOptions : [],
        selectedContent: col.selectedContent || null,
        alternativeSelectedContent: col.alternativeSelectedContent || null,
        activeSelectedContent: col.activeSelectedContent || null,
        activeContentOptions: Array.isArray(col.activeContentOptions) ? col.activeContentOptions : [],
        selectedContentIndex: col.selectedContentIndex ?? 0,
        showBorder: col.showBorder !== undefined ? col.showBorder : true,
        isCard: col.isCard || false,
      })),
      height: row.height || "20%",
    }));
  }

  @HostListener("document:click", ["$event"])
  public onDocumentClick(event: Event): void {
    // Cierra los menús de opciones en las celdas
    this.gridData.forEach((row) => {
      row.columns.forEach((col: any) => {
        col.showOptionsMenu = false;
      });
    });
  }



  getSelectedRow(): any {
    return this.gridData.find((row) => row.columns.includes(this.selectedCell));
  }

  adjustColSpan(change: number) {
    if (this.selectedCell) {
      this.selectedCell.colSpan = Math.max(
        1,
        (this.selectedCell.colSpan || 1) + change
      );
    }
  }

  adjustRowSpan(change: number) {
    if (this.selectedCell) {
      this.selectedCell.rowSpan = Math.max(
        1,
        (this.selectedCell.rowSpan || 1) + change
      );
    }
  }


  configToastr() {
    this.toastr.toastrConfig.positionClass = "toast-top-full-width";
    this.toastr.toastrConfig.extendedTimeOut = 0;
    this.toastr.toastrConfig.timeOut = 2900;
  }


  getGridTemplateColumns(columns: any[]): string {
    return columns.map((col) => col.width).join(" ");
  }

  getGridTemplateRows(): string {
    return this.gridData.map((row) => row.height).join(" ");
  }

  validateAndApplyJson() {
    try {
      const parsedData = JSON.parse(this.gridDataString);
      this.gridData = parsedData;
      this.jsonValid = true;
      setTimeout(() => this.renderDynamicComponents(), 100);
    } catch (error) {
      this.jsonValid = false;
      this.toastr.warning(`Error al procesar el JSON: ${error}`, "Advertencia");
    }
  }

  renderDynamicComponents(
    specificRowIndex?: number,
    specificColIndex?: number
  ): void {
    setTimeout(() => {
      if (!this.dynamicComponentContainers) return;

      let containerIndex = 0;
      this.gridData.forEach((row, rowIndex) => {
        row.columns.forEach((col: any, colIndex: number) => {
          if (col.isComponent) {
            if (
              specificRowIndex === undefined ||
              (rowIndex === specificRowIndex && colIndex === specificColIndex)
            ) {
              const container =
                this.dynamicComponentContainers.get(containerIndex);
              if (container) {
                container.clear();
                this.createComponent(container, col, rowIndex, colIndex);
              }
            }
            containerIndex++;
          }
        });
      });
    }, 100);
  }

  async createComponent(
    container: ViewContainerRef,
    col: any,
    rowIndex: number,
    colIndex: number
  ): Promise<void> {
    this.spinner.show();
    try {
      const spinnerName = `spinner-${rowIndex}-${colIndex}`;
      this.spinner.show(spinnerName);

      let kpiNumber;

      if (col.activeSelectedContent) {
        kpiNumber = col.activeSelectedContent.value;
      } else {
        // Si por alguna razón no está definido, intentar asignarlo
        // Posibles casos de selección simple sin switch
        if (col.selectedContent) {
          kpiNumber = col.selectedContent.value;
        } else {
          throw new Error("No se pudo determinar el kpiNumber");
        }
      }

      const ComponentType = await this.loadDynamicComponent(
        `kpi-${kpiNumber}`,
        col
      );
      const componentRef = container.createComponent(ComponentType);

      (componentRef.instance as any).kpiNumber = kpiNumber;
      (componentRef.instance as any).theme = this.selectedTheme.value;

      // Almacenar la referencia del componente dinámico
      this.dynamicComponents.push({
        componentRef: componentRef,
        rowIndex: rowIndex,
        colIndex: colIndex,
      });

      if ((componentRef.instance as any).isCard) {
        col.isCard = true;
      } else {
        col.isCard = false;
      }
    this.changeDetectorRef.detectChanges();

      this.spinner.hide(spinnerName);
    } catch (error) {
      this.spinner.hide(`spinner-${rowIndex}-${colIndex}`);
      this.toastr.error(`Error al crear el componente.`);
      console.error("Error al crear el componente:", error);
    }
  }

  private async loadDynamicComponent(
    componentName: string,
    col: any
  ): Promise<any> {
    let kpiNumber = parseInt(componentName.split("-")[1]);
    let kpiRequest = {
      listKpi: [kpiNumber],
      filtros: {
        w: {
          fechaDesde: "2023-01-01",
          fechaHasta: "2023-12-31",
        },
      },
    };
    // TO TEST COMPONENTS WITHOUT ENDPOINT. REMOVE WHEN ENDPOINTS ARE DONE
    if ([1001, 1002, 1003, 1004].includes(kpiNumber)) {
      kpiRequest = { ...kpiRequest, listKpi: [6] };
    }
    const data = await this.kpiService.getKPI(kpiRequest).toPromise();
    console.log("🔰 ~ loadDynamicComponent ~ data:", data);

    let representacion: string | null = null;
    if (data.representacion) {
      representacion = data.representacion.toLowerCase();
    }

    if (!representacion) {
      this.toastr.error(
        `No se encuentra "representacion" en el json para: ${componentName}`
      );
      throw new Error(
        `No se encuentra "representacion" en el json para: ${componentName}`
      );
    }

    if (data.botones) {
      col.buttons = data.botones;
    }

    try {
      // TO TEST COMPONENTS WITHOUT ENDPOINT. REMOVE WHEN ENDPOINTS ARE DONE
      const representationsMap: { [key: number]: string } = {
        1001: "img",
        1002: "cardObj",
        1003: "line",
        1004: "html",
      };

      representacion = representationsMap[kpiNumber] || representacion;
      const module = await import(
        `../tabs/kpis/kpi-${representacion}/kpi-${representacion}.component`
      );
      const componentKey = Object.keys(module).find(
        (key) =>
          key.toLowerCase() === `kpi${representacion?.toLowerCase()}component`
      );
      console.log(module);

      if (!componentKey) {
        throw new Error(`No se encontró el componente para: ${representacion}`);
      }

      return module[componentKey];
    } catch (error) {
      console.error(`Error al cargar el componente: ${error}`);
      this.toastr.error(
        `No se pudo cargar el componente para: ${representacion}`
      );
      throw error;
    }
  }


  toggleMultiSelection(col: any) {
    col.multiSelection = !col.multiSelection;

    if (col.multiSelection) {
      // Al activar la multiselección, vaciamos 'col.content', 'col.contentOptions', y 'col.selectedContent'
      col.content = [];
      col.contentOptions = [];
      col.selectedContent = null;
    } else {
      // Mantener col.content como array incluso en selección simple
      if (col.content.length > 1) {
        // Si hay más de un elemento, cgemos el primero
        col.content = [col.content[0]];
        col.contentOptions = [col.contentOptions[0]];
        col.selectedContent = col.contentOptions[0] || null;
      } else if (col.content.length === 0) {
        // Si está vacío, inicializamos
        col.content = [];
        col.contentOptions = [];
        col.selectedContent = null;
      }
    }
  }

  switchComponent(col: any, side: string) {
    col.switchActiveSide = side;

    if (col.multiSelection && col.showSwitch) {
      // Ambos activos
      if (col.selectedContentIndex === undefined) {
        col.selectedContentIndex = 0;
      }

      if (side === "left") {
        col.activeContentOptions = col.contentOptions;
        col.activeSelectedContent =
          col.contentOptions[col.selectedContentIndex] || col.contentOptions[0];
        col.selectedContent = col.activeSelectedContent;
      } else {
        col.activeContentOptions = col.alternativeContentOptions;
        col.activeSelectedContent =
          col.alternativeContentOptions[col.selectedContentIndex] ||
          col.alternativeContentOptions[0];
        col.alternativeSelectedContent = col.activeSelectedContent;
      }
    } else if (col.showSwitch) {
      // Solo switch
      if (side === "left") {
        col.activeSelectedContent = col.selectedContent;
      } else {
        col.activeSelectedContent = col.alternativeSelectedContent;
      }
    }

    const { rowIndex, colIndex } = this.getCellIndices(col);
    this.renderDynamicComponents(rowIndex, colIndex);
  }


  private extractKpiNumber(value: string | number): number {
    if (typeof value === "number") {
      return value;
    }

    const match = value.match(/\d+/);
    return match ? parseInt(match[0], 10) : 0;
  }

  cellclicktemp() {
    let dialogRef = this.dialog.open(KpiPivotgridComponent, {
      height: "95vh",
      width: "80vw",
    });
  }

  showInfoPanel(col: any, rowIndex: number, colIndex: number) {
    // Si el panel que se intenta abrir ya está abierto, lo cerramos
    if (
      this.activeCellInfo &&
      this.activeCellInfo.rowIndex === rowIndex &&
      this.activeCellInfo.colIndex === colIndex
    ) {
      this.hideInfoPanel(col);
      return;
    }

    // Cerrar el panel activo si existe
    this.hideInfoPanel(this.activeCellInfo?.col);

    // Obtener el kpiNumber activo
    const kpiNumber = this.getActiveKpiNumber(col);

    // Obtener la información del KPI
    this.kpiService.getKPIUse(kpiNumber).subscribe({
      next: (info) => {
        // Abrir el nuevo panel con la información obtenida
        this.activeCellInfo = { rowIndex, colIndex, col, info: info.obj };
        col.isPanelVisible = true;
        setTimeout(() => {
          col.showInfoPanel = true;
        }, 50); // Retraso para asegurar que `ngIf` cargue el panel antes de animarlo
      },
      error: (error) => {
        this.toastr.error("Error al obtener la información del KPI", "Error");
        console.error("Error al obtener la información del KPI:", error);
      },
    });
  }

  hideInfoPanel(col?: any) {
    if (!col) return;

    col.showInfoPanel = false;

    setTimeout(() => {
      col.isPanelVisible = false;
      if (this.activeCellInfo && this.activeCellInfo.col === col) {
        this.activeCellInfo = null;
      }
    }, 500);
  }

  toggleCellExpansion(col: any) {
    if (col.isExpanded) {
      col.isContracting = true;
      setTimeout(() => {
        col.isExpanded = false;
        col.isContracting = false;
      }, 200); // Duración de la animación
    } else {
      col.isExpanding = true;
      setTimeout(() => {
        col.isExpanded = true;
        col.isExpanding = false;
      }, 200); // Duración de la animación
    }
  }

  public toggleOptionsMenu(col: any, event: Event): void {
    event.stopPropagation();

    col.showOptionsMenu = !col.showOptionsMenu;

    if (col.showOptionsMenu) {
      this.gridData.forEach((row) => {
        row.columns.forEach((c: any) => {
          if (c !== col) {
            c.showOptionsMenu = false;
          }
        });
      });
    }
  }

  selectQuarter(quarter: number) {
    const year = new Date().getFullYear();
    switch (quarter) {
      case 1:
        this.startDate = `${year}-01-01`;
        this.endDate = `${year}-03-31`;
        break;
      case 2:
        this.startDate = `${year}-04-01`;
        this.endDate = `${year}-06-30`;
        break;
      case 3:
        this.startDate = `${year}-07-01`;
        this.endDate = `${year}-09-30`;
        break;
      case 4:
        this.startDate = `${year}-10-01`;
        this.endDate = `${year}-12-31`;
        break;
    }
  }
}


<div class="dashboard-container">

  <div class="main-container">
    <div class="main" [ngClass]="activeCustomTheme" [style.grid-template-rows]="getGridTemplateRows()">
      <div *ngFor="let row of gridData; let rowIndex = index" class="row" [ngStyle]="{
          'grid-template-columns': getGridTemplateColumns(row.columns)
        }">
        <div *ngFor="let col of row.columns; let colIndex = index" class="column" [ngClass]="getDynamicClasses(col)"
          [ngStyle]="{
            'grid-row': col.rowSpan ? 'span ' + col.rowSpan : 'auto',
            'grid-column': col.colSpan ? 'span ' + col.colSpan : 'auto',
            border: col.showBorder ? null : '0'
          }">

          <ng-container *ngIf="!col.isComponent">{{
            col.content[0]
            }}</ng-container>
          <ng-container *ngIf="col.isComponent">
            <div class="cell-content">
              <!-- ng-select for single selection in non-multiselection mode -->
              <div class="cell-options-container flex-row" [ngClass]="{
                  'no-ng-select': !col.multiSelection && col.isCard
                }">
                <div class="cell-menu-container">
                  <button *ngIf="!col.isCard" class="cell-button" (click)="toggleOptionsMenu(col, $event)">
                    <span class="material-symbols-outlined"> more_vert </span>
                  </button>

                  <!-- Botón de contraer siempre visible al expandir -->
                  <button *ngIf="col.isExpanded" class="cell-button" (click)="toggleCellExpansion(col)">
                    <span class="material-symbols-outlined">close_fullscreen</span>
                  </button>

                  <!-- Menú de opciones -->
                  <div class="options-menu" [ngClass]="{ show: col.showOptionsMenu }"
                    (click)="$event.stopPropagation()">
                    <button class="options-menu-item" (click)="
                        showInfoPanel(col, rowIndex, colIndex);
                        toggleOptionsMenu(col, $event)
                      ">
                      <span class="material-symbols-outlined">info</span>
                      <span>Información</span>
                    </button>
                    <button class="options-menu-item" *ngIf="col.buttons.drillDown"
                      (click)="cellclicktemp(); toggleOptionsMenu(col, $event)">
                      <span class="material-symbols-outlined">table_view</span>
                      <span>Mostrar datos</span>
                    </button>
                    <button class="options-menu-item" *ngIf="col.buttons.fullSize" (click)="
                        toggleCellExpansion(col); toggleOptionsMenu(col, $event)
                      ">
                      <span class="material-symbols-outlined">{{
                        col.isExpanded ? "close_fullscreen" : "open_in_full"
                        }}</span>
                      <span>{{
                        col.isExpanded ? "Restaurar" : "Expandir"
                        }}</span>
                    </button>
                  </div>
                </div>
                <!-- Ng-Select para selección simple -->
                <ng-select *ngIf="col.multiSelection === false && !col.isCard" class="kpi-select-disabled"
                  id="kpi-select-viewer" [(ngModel)]="col.selectedContent" [items]="col.contentOptions"
                  [disabled]="true" [multiple]="false" [clearable]="false" [searchable]="false"
                  (change)="onMultiselectionListChange($event, col)">
                  <ng-template ng-label-tmp let-item="item">
                    {{ getLabelTitle(item.label) }}
                  </ng-template>
                </ng-select>

                <!-- Ng-Select para multiselección con soporte para Switch -->
                <ng-select *ngIf="col.multiSelection" class="kpi-select-multi" id="kpi-select-viewer"
                  [(ngModel)]="col.activeSelectedContent" [items]="col.activeContentOptions" [multiple]="false"
                  [clearable]="false" [searchable]="false" (change)="onMultiselectionListChange($event, col)">
                  <ng-template ng-label-tmp let-item="item">
                    {{ getLabelTitle(item.label) }}
                  </ng-template>
                </ng-select>

                <!-- Switch -->
                <div *ngIf="col.showSwitch" class="switch">
                  <div class="left" [class.active]="col.switchActiveSide === 'left'"
                    (click)="switchComponent(col, 'left')">
                    {{ col.switchLeftText }}
                  </div>
                  <div class="right" [class.active]="col.switchActiveSide === 'right'"
                    (click)="switchComponent(col, 'right')">
                    {{ col.switchRightText }}
                  </div>
                </div>

                <!-- Info Panel -->
                <div class="info-panel info-panel-{{ rowIndex }}-{{ colIndex }}" [class.show]="col.showInfoPanel"
                  *ngIf="col.isPanelVisible">
                  <div class="info-icon">
                    <span class="material-symbols-outlined"> info </span>
                  </div>
                  <div class="info-text">
                    <p>
                      {{
                      activeCellInfo?.info || "No hay información disponible."
                      }}
                    </p>
                    <button (click)="hideInfoPanel(col)">Cerrar</button>
                  </div>
                </div>
              </div>
              <div class="component-container">
                <ng-container #dynamicComponentContainer>
                  <ngx-spinner [name]="'spinner-' + rowIndex + '-' + colIndex" bdColor="rgba(51,51,51,0.3)"
                    size="medium" color="#fff" type="ball-scale-multiple" [fullScreen]="false">
                  </ngx-spinner>
                </ng-container>
              </div>
            </div>
          </ng-container>
        </div>
      </div>
    </div>
  </div>
</div>

<div class="nav-bar-container">
  <app-nav-grid-viewer
    (addSheetEvent)="onAddGrid($event)"
  ></app-nav-grid-viewer>
</div>
<mat-tab-group
  class="tab-group"
  [selectedIndex]="selectedTab"
  *ngIf="activeGrids?.length > 0; else noData"
>
  <mat-tab
    *ngFor="let grid of activeGrids"
    mat-stretch-tabs
    style="width: 100%"
  >
    <ng-template mat-tab-label>
      <div class="grid-tab-label">
        {{ grid.name }}
        <mat-icon class="example-tab-icon" (click)="removeGrid(grid)"
          >close</mat-icon
        >
      </div>
    </ng-template>
    <ng-template matTabContent>
      <div class="tab-template">
        <app-grid-viewer
          [isActiveTab]="grid.index - 1 === selectedTab"
        ></app-grid-viewer>
      </div>
    </ng-template>
  </mat-tab>
</mat-tab-group>

<ng-template #noData>
  <div class="tab-template">
    <p class="no-data-msg">No hay grids añadidos.</p>
  </div>
</ng-template>