Ayuda en codigo Choices

Angelicahg

Gamma
Verificado
Verificación en dos pasos activada
Verificado por Whatsapp
Verificado por Binance
Hola, estoy desarrollando una página de prácticas que incluye un catálogo público y otro privado. Desde el catálogo privado puedo añadir, editar y gestionar modelos que luego se muestran en el catálogo público. Cada modelo tiene filtros asociados, como color, temática, etc. Al hacer clic en "Editar", necesito que se cargue automáticamente toda la información del modelo (nombre, precio, temática, imágenes, etc.).

Hasta ahora he logrado que todo se cargue correctamente, excepto los filtros de color. En el backend los valores sí se reflejan (como se muestra en la imagen de la derecha en la consola), pero visualmente no se seleccionan en el filtro de color. En la segunda imagen muestro cómo deberían aparecer por defecto.

Lo que está marcado en verde es el resultado que quiero lograr: que al entrar a editar un modelo, se vean seleccionados por defecto los colores que ya tiene asignados. He intentado varias soluciones, incluso con ayuda de ChatGPT, pero aún no encuentro cómo resolverlo. Creo que el problema es principalmente visual o del lado del frontend




Lo marcado en verde quiero que salga asi cada vez que de en editar osea que se vea reflejado los colores que tiene






Mis codigos

editar_modelo_completo.php
PHP:
document.addEventListener("DOMContentLoaded", async () => {
  const id = new URLSearchParams(window.location.search).get("id");
  const form = document.getElementById("form-editar-modelo");
  const quill = new Quill("#editor-quill", { theme: "snow" });

  const colorSelect = document.getElementById("color");
  const tematicaSelect = document.getElementById("tematica");

  let colorChoices = null;
  let tematicaChoices = null;

  try {
    const res = await fetch("controllers/catalogo/obtener_modelos.php?id=" + id);
    const data = await res.json();
    console.log("📦 Datos recibidos:", data);

    if (!data.success || !data.modelo) {
      throw new Error(data.message || "Modelo no encontrado");
    }

    const modelo = data.modelo;

    document.getElementById("modelo_id").value = modelo.id;
    document.getElementById("titulo").value = modelo.titulo;
    document.getElementById("precio").value = modelo.precio;
    document.getElementById("tipo").value = modelo.tipo || "boda";
    document.getElementById("publico").checked = modelo.publico == 1;
    quill.root.innerHTML = modelo.descripcion || "";

    if (modelo.imagen) {
      document.getElementById("preview-portada").innerHTML =
        `<img src="${modelo.imagen}" style="max-width:100%; border-radius:8px;">`;
    }

    // ✅ Inicializar Choices para Colores desde <option> en HTML
    colorChoices = new Choices(colorSelect, {
      removeItemButton: true,
      searchEnabled: false,
      itemSelectText: '',
      shouldSort: false,
    });

    // ✅ Marcar visualmente los colores seleccionados desde el modelo
    if (Array.isArray(modelo.colores)) {
      setTimeout(() => {
        colorChoices.removeActiveItems();
        modelo.colores.forEach(color => {
          colorChoices.setChoiceByValue(color.toLowerCase());
        });
      }, 100); // Espera corta para que Choices termine de inicializar
    }

    // ✅ Temática visual
    if (Array.isArray(modelo.tematicas) && modelo.tematicas.length > 0 && tematicaSelect) {
      const valor = modelo.tematicas[0];
      const option = tematicaSelect.querySelector(`option[value="${valor}"]`);
      if (option) option.selected = true;

      const instanciaTematica = (window.Choices?.instances || []).find(c => c.passedElement.element.id === "tematica");
      if (instanciaTematica) instanciaTematica.destroy();

      tematicaChoices = new Choices(tematicaSelect, {
        searchEnabled: false,
        itemSelectText: '',
        shouldSort: false,
      });
    }

    // ✅ Galería de imágenes
    const galeria = modelo.galeria || [];
    if (galeria.length > 0) {
      const contenedor = document.getElementById("galeria-existente");
      galeria.forEach(img => {
        const div = document.createElement("div");
        div.className = "item-galeria";
        div.innerHTML = `<img src="${img.imagen}" style="max-width: 120px; border-radius: 6px;">`;
        contenedor.appendChild(div);
      });
    }

    // ✅ Envío del formulario
    form.addEventListener("submit", async (e) => {
      e.preventDefault();
      const formData = new FormData(form);
      formData.set("descripcion", quill.root.innerHTML);

      // Colores seleccionados
      if (colorChoices) {
        colorChoices.getValue(true).forEach(valor => {
          formData.append("colores[]", valor);
        });
      }

      // Temática seleccionada
      if (tematicaChoices) {
        const tematica = tematicaChoices.getValue(true);
        if (tematica?.length > 0) {
          formData.append("tematicas[]", tematica[0]);
        }
      }

      if (typeof mostrarModalCargando === 'function') mostrarModalCargando("Guardando modelo...");

      try {
        const res = await fetch("controllers/catalogo/editar_modelo.php", {
          method: "POST",
          body: formData
        });
        const result = await res.json();
        console.log("✅ Respuesta al guardar:", result);

        if (typeof ocultarModalCargando === 'function') ocultarModalCargando();

        if (result.success) {
          if (typeof mostrarModal === 'function') {
            mostrarModal({
              icono: "✅",
              titulo: "¡Guardado!",
              mensaje: "El modelo fue actualizado correctamente.",
              onClose: () => window.location.href = "index.php?page=catalogo"
            });
          } else {
            alert("Modelo guardado correctamente.");
            window.location.href = "index.php?page=catalogo";
          }
        } else {
          throw new Error(result.message);
        }

      } catch (err) {
        if (typeof ocultarModalCargando === 'function') ocultarModalCargando();
        console.error("❌ Error en envío:", err);
        if (typeof mostrarModalError === 'function') {
          mostrarModalError({ titulo: "Error", mensaje: err.message || "No se pudo guardar." });
        } else {
          alert("Error al guardar modelo: " + err.message);
        }
      }
    });

  } catch (err) {
    console.error("❌ Error al cargar modelo:", err);
    if (typeof mostrarModalError === 'function') {
      mostrarModalError({
        titulo: "Error inesperado",
        mensaje: "No se pudo cargar el modelo. Intenta más tarde."
      });
    } else {
      alert("Error general al cargar.");
    }
  }
});

form-editar-modelo.php


PHP:
<?php
require_once __DIR__ . '/../../config/config.php';
require_once __DIR__ . '/../../config/db.php';

$idModelo = $_GET['id'] ?? null;
?>

<!-- PANEL EDICIÓN DE MODELO -->
<section class="panel-catalogo edicion-modelo-activa">
  <h2 class="titulo-edicion">Editar modelo</h2>

  <form id="form-editar-modelo" method="post" enctype="multipart/form-data" class="formulario-base formulario-catalogo">
    <input type="hidden" name="modelo_id" id="modelo_id">

    <!-- Vista previa portada -->
    <div>
      <label>Imagen actual:</label>
      <div id="preview-portada" class="preview-portada"></div>
    </div>

    <!-- Portada -->
    <div>
      <label for="imagen">Cambiar imagen de portada:</label>
      <div class="input-dropzone">
        Haz clic o arrastra una imagen aquí
        <input type="file" id="imagen" name="imagen" accept="image/*">
      </div>
    </div>
   
    <!-- Galería -->
    <div>
      <label for="galeria">Imágenes adicionales:</label>
      <div class="input-dropzone">
        Haz clic o arrastra aquí para subir imágenes
        <input type="file" id="galeria" name="galeria[]" multiple accept="image/*">
      </div>
      <div id="galeria-existente" class="galeria-actual"></div>
    </div>
   
    <!-- Título y precio -->
    <div class="grupo-doble">
      <div>
        <label for="titulo">Nombre del modelo:</label>
        <input type="text" id="titulo" name="titulo" required>
      </div>
      <div>
        <label for="precio">Precio:</label>
        <input type="number" id="precio" name="precio" step="0.01" required>
      </div>
    </div>

    <!-- Tipo de evento -->
    <div>
      <label for="tipo">Tipo de evento:</label>
      <select id="tipo" name="tipo" required>
        <option value="">Selecciona una opción</option>
        <option value="boda">Boda</option>
        <option value="quinceaños">Quinceaños</option>
        <option value="graduación">Graduación</option>
        <option value="aniversario">Aniversario</option>
        <option value="babyshower">BabyShower</option>
        <option value="cumpleaños">Cumpleaños</option>
        <option value="evento">Eventos</option>
      </select>
    </div>
   
    <!-- Selects múltiples -->
    <div class="grupo-doble">
      <div>
        <label for="color">Colores:</label>
        <select id="color" name="colores[]" multiple class="select-filtro" data-preservar>
          <option value="azul">Azul</option>
          <option value="rojo">Rojo</option>
          <option value="verde">Verde</option>
          <option value="dorado">Dorado</option>
          <option value="rosa">Rosa</option>
          <option value="negro">Negro</option>
          <option value="blanco">Blanco</option>
          <option value="morado">Morado</option>
          <option value="plateado">Plateado</option>
        </select>
      </div>
      <div>
        <label for="tematica">Temática visual:</label>
        <select id="tematica" name="tematicas[]" class="select-filtro" required>
          <option value="">Selecciona una opción</option>
          <option value="flores">Flores</option>
          <option value="mariposas">Mariposas</option>
          <option value="discoteca">Discoteca</option>
          <option value="princesas">Princesas</option>
          <option value="clasico">Clásico</option>
          <option value="moderno">Moderno</option>
          <option value="acuarela">Acuarela</option>
        </select>
      </div>
    </div>
    <!-- Descripción -->
    <div>
      <label for="descripcion">Descripción larga:</label>
      <div id="editor-quill"></div>
      <input type="hidden" name="descripcion" id="descripcion">
    </div>





    <!-- Visibilidad -->
    <div class="switch-contenedor">
      <label class="switch">
        <input type="checkbox" name="publico" id="publico">
        <span class="slider round"></span>
      </label>
      <span class="switch-label">Mostrar en catálogo público</span>
    </div>

    <!-- Token -->
    <input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">

    <!-- Botones -->
    <div class="fila-botones">
      <button type="submit" class="btn">
        <span class="btn-text">Guardar cambios</span>
        <span class="spinner" style="display: none;"></span>
      </button>
      <a href="index.php?page=catalogo" class="btn secundario">Volver al catálogo</a>
    </div>
  </form>
</section>

<!-- Quill -->
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
<script src="https://cdn.quilljs.com/1.3.6/quill.min.js"></script>

init_choices.js


JavaScript:
// ✅ init_choices.js - Inicialización de Choices.js con estilos personalizados y banderas

document.addEventListener('DOMContentLoaded', () => {
  const skipIds = ['codigo_pais', 'filtro-color', 'filtro-tematica', 'filtro-orden', 'tematica', 'tipo'];

  // ✅ Inicializar todos los selects genéricos (excepto los ignorados)
  document.querySelectorAll('.select-filtro').forEach(select => {
    if (skipIds.includes(select.id)) return;

    const isMultiple = select.multiple;

    const choicesInstance = new Choices(select, {
      removeItemButton: isMultiple,
      searchEnabled: false,
      itemSelectText: '',
      shouldSort: false
    });

    select.closest('.choices')?.classList.add('filtro-personalizado');
  });

  // ✅ Código país con banderas
  const codigoPaisSelect = document.getElementById('codigo_pais');
  if (codigoPaisSelect) {
    new Choices(codigoPaisSelect, {
      searchEnabled: false,
      itemSelectText: '',
      shouldSort: false,
      allowHTML: true,
      callbackOnCreateTemplates: function (template) {
        return {
          item: function (classNames, data) {
            const flag = data.customProperties?.flag || '';
            return template(`
              <div class="${classNames.item} ${classNames.itemSelectable}">
                <span class="fi fi-${flag} bandera-circle" style="margin-right: 6px;"></span>${data.label}
              </div>
            `);
          },
          choice: function (classNames, data) {
            const flag = data.customProperties?.flag || '';
            return template(`
              <div class="${classNames.item} ${classNames.itemChoice}" data-choice data-id="${data.id}" data-value="${data.value}" ${data.active ? 'aria-selected="true"' : ''}>
                <span class="fi fi-${flag} bandera-circle" style="margin-right: 6px;"></span>${data.label}
              </div>
            `);
          }
        };
      }
    });
  }

  // ✅ Plantilla con íconos HTML (para filtros públicos)
  const renderHTMLTemplate = (template) => ({
    item: (classNames, data) => {
      const icon = data.customProperties?.icon || '';
      return template(`
        <div class="${classNames.item} ${classNames.itemSelectable}">
          ${icon} ${data.label}
        </div>
      `);
    },
    choice: (classNames, data) => {
      return template(`
        <div class="${classNames.item} ${classNames.itemChoice}" data-choice data-id="${data.id}" data-value="${data.value}" ${data.active ? 'aria-selected="true"' : ''}>
          ${data.label}
        </div>
      `);
    }
  });

  // ✅ Inicializar filtros públicos con íconos
  const initPublicFilterWithIcons = (id, globalVarName) => {
    const el = document.getElementById(id);
    if (!el) return;

    const options = Array.from(el.options).map(opt => ({
      value: opt.value,
      label: opt.textContent,
      selected: opt.selected,
      disabled: opt.disabled,
      customProperties: {
        icon: opt.dataset.icon || ''
      }
    }));

    // ❌ NO borres las <option> si el select será reutilizado en modo edición
    if (!el.hasAttribute("data-preservar")) {
      el.innerHTML = '';
    }

    window[globalVarName] = new Choices(el, {
      searchEnabled: false,
      itemSelectText: '',
      shouldSort: false,
      allowHTML: true,
      callbackOnCreateTemplates: renderHTMLTemplate,
      choices: options
    });
  };

  // ✅ Filtros públicos con íconos
  initPublicFilterWithIcons('filtro-color', 'choicesColor');
  initPublicFilterWithIcons('filtro-tematica', 'choicesTematica');
  initPublicFilterWithIcons('filtro-orden', 'choicesOrden');
});

// ✅ Corrige íconos tras reinicio
function sincronizarIconoSelect(choicesInstance) {
  const selected = choicesInstance.getValue(true);
  const option = choicesInstance.config.choices.find(c => c.value === selected);
  const icon = option?.customProperties?.icon || '';

  const items = document.querySelectorAll(`#${choicesInstance.passedElement.element.id} + .choices .choices__inner .choices__item`);

  if (items.length > 0 && icon) {
    items[0].innerHTML = `${icon} ${option.label}`;
  }
}

datos_choices.js


JavaScript:
// 📁 public/js/global/datos_choices.js

export const opcionesColor = [
  { value: '', label: 'Color', selected: true, disabled: true, customProperties: { icon: '<i class="fa-solid fa-palette"></i>' } },
  { value: 'azul', label: 'Azul' },
  { value: 'rosa', label: 'Rosa' },
  { value: 'verde', label: 'Verde' },
  { value: 'dorado', label: 'Dorado' },
  { value: 'plateado', label: 'Plateado' },
  { value: 'blanco', label: 'Blanco' },
  { value: 'negro', label: 'Negro' },
  { value: 'rojo', label: 'Rojo' },
  { value: 'fucsia', label: 'Fucsia' },
  { value: 'celeste', label: 'Celeste' },
  { value: 'morado', label: 'Morado' },
  { value: 'amarillo', label: 'Amarillo' },
  { value: 'beige', label: 'Beige' }
];

export const opcionesTematica = [
  { value: '', label: 'Temática', selected: true, disabled: true, customProperties: { icon: '<i class="fa-solid fa-brush"></i>' } },
  { value: 'flores', label: 'Flores' },
  { value: 'mariposas', label: 'Mariposas' },
  { value: 'discoteca', label: 'Discoteca' },
  { value: 'princesas', label: 'Princesas' },
  { value: 'clasico', label: 'Clásico' },
  { value: 'moderno', label: 'Moderno' },
  { value: 'acuarela', label: 'Acuarela' }
];

export const opcionesOrden = [
  { value: '', label: 'Ordenar', selected: true, disabled: true, customProperties: { icon: '<i class="fa-solid fa-arrow-down-wide-short"></i>' } },
  { value: 'az', label: 'A-Z' },
  { value: 'za', label: 'Z-A' },
  { value: 'recientes', label: 'Más reciente' },
  { value: 'viejos', label: 'Más viejo' }
];

Si alguien sabe se lo agradeceria