// AGENTFORGE-IA — pantalla Implantación
const Deployment = ({ preselectedAgent }) => {
  const [filter, setFilter] = React.useState('todas');
  const [selected, setSelected] = React.useState(null);
  const [showAssistant, setShowAssistant] = React.useState(false);
  const [showCompare, setShowCompare] = React.useState(false);
  const [showSales, setShowSales] = React.useState(false);
  const [showPdf, setShowPdf] = React.useState(false);
  const [assistantAnswers, setAssistantAnswers] = React.useState({
    sector: 'general',
    data: 'standard',
    team: 'small',
    budget: 'starter',
  });
  const [requests, setRequests] = React.useState([]);
  const [sendingRequest, setSendingRequest] = React.useState(false);
  const [deployments, setDeployments] = React.useState([]);
  const [showDeploymentsList, setShowDeploymentsList] = React.useState(false);
  const [showNewDeploy, setShowNewDeploy] = React.useState(false);
  const emptyDeployForm = {
    agent_id: '',
    channel: 'web',
    whatsapp_access_token: '',
    whatsapp_phone_number_id: '',
    whatsapp_verify_token: '',
    telegram_bot_token: '',
    allowed_origins: '*',
    system_prompt_override: '',
  };
  const [deployForm, setDeployForm] = React.useState(emptyDeployForm);
  const [deployFormLoading, setDeployFormLoading] = React.useState(false);
  const [agents, setAgents] = React.useState([]);
  const [agentSearch, setAgentSearch] = React.useState('');
  const [pendingDeployAgent, setPendingDeployAgent] = React.useState(null);
  const [lastCreatedDeployment, setLastCreatedDeployment] = React.useState(null);
  const [deployFeedback, setDeployFeedback] = React.useState(null);
  const [copiedToken, setCopiedToken] = React.useState(null);
  const notify = (msg) => window.afNotify && window.afNotify(msg);
  const contextPromptFromForm = (ctx) => {
    if (!ctx) return '';
    const lines = [];
    if (ctx.empresa) lines.push(`Empresa: ${ctx.empresa}`);
    if (ctx.descripcion) lines.push(`Descripción del negocio: ${ctx.descripcion}`);
    if (ctx.productos) lines.push(`Productos/Servicios: ${ctx.productos}`);
    if (ctx.audiencia) lines.push(`Audiencia objetivo: ${ctx.audiencia}`);
    if (ctx.tono) lines.push(`Tono recomendado: ${ctx.tono}`);
    if (ctx.faqs) lines.push(`FAQs: ${ctx.faqs}`);
    if (ctx.instrucciones) lines.push(`Instrucciones extra: ${ctx.instrucciones}`);
    if (lines.length === 0) return '';
    return `Contexto del cliente para este agente:\n${lines.join('\n')}\n\nUsa este contexto para responder con precisión y en el idioma del usuario.`;
  };
  const loadAgentContextBySlug = (slug) => {
    if (!slug) return null;
    try {
      const raw = localStorage.getItem('af_agent_ctx_' + slug);
      if (!raw) return null;
      return JSON.parse(raw);
    } catch (_) {
      return null;
    }
  };

  const getToken = () => sessionStorage.getItem('af_token') || '';

  /**
   * Carga solicitudes de implantación del usuario autenticado.
   * @returns {Promise<void>}
   */
  const loadRequests = React.useCallback(async () => {
    const token = getToken();
    if (!token) return;
    try {
      const res = await fetch('/api/v1/deployment/requests/mine', {
        headers: { Authorization: 'Bearer ' + token },
      });
      if (!res.ok) {
        notify('No se pudieron cargar solicitudes de implantación');
        return;
      }
      const data = await res.json();
      setRequests(Array.isArray(data) ? data.slice().reverse() : []);
    } catch (_) {
      notify('Error de red cargando solicitudes de implantación');
    }
  }, []);

  const loadDeployments = React.useCallback(async () => {
    const token = getToken();
    if (!token) return;
    try {
      const res = await fetch('/api/v1/deployment/deployments', {
        headers: { Authorization: 'Bearer ' + token },
      });
      if (!res.ok) {
        notify('No se pudieron cargar despliegues');
        return;
      }
      const data = await res.json();
      setDeployments(Array.isArray(data) ? data : []);
    } catch (_) {
      notify('Error de red cargando despliegues');
    }
  }, []);

  const loadAgents = React.useCallback(async () => {
    try {
      const token = getToken();
      const [catalogRes, purchasedRes] = await Promise.all([
        fetch('/api/v1/agents'),
        token ? fetch('/api/v1/purchases/my-agents', { headers: { Authorization: 'Bearer ' + token } }) : Promise.resolve(null),
      ]);
      if (!catalogRes.ok) {
        notify('No se pudo cargar el catálogo de agentes');
      }
      if (purchasedRes && !purchasedRes.ok) {
        notify('No se pudieron cargar tus agentes comprados');
      }
      const catalogData = catalogRes.ok ? await catalogRes.json() : [];
      const catalogAgents = Array.isArray(catalogData) ? catalogData : (Array.isArray(catalogData.agents) ? catalogData.agents : []);
      const purchasedData = purchasedRes && purchasedRes.ok ? await purchasedRes.json() : [];
      const purchasedAgents = (Array.isArray(purchasedData) ? purchasedData : []).map((item) => ({
        id: item.agent_id,
        slug: item.agent_slug,
        name: item.agent_name,
        tagline: `Agente instalado · ${item.plan || 'plan activo'}`,
        source: 'installed',
      }));
      const byId = new Map();
      [...purchasedAgents, ...catalogAgents].forEach((agent) => {
        if (agent?.id && !byId.has(agent.id)) byId.set(agent.id, agent);
      });
      setAgents(Array.from(byId.values()));
    } catch (_) {
      notify('Error de red cargando agentes');
    }
  }, []);

  /**
   * Normaliza texto para que la búsqueda no falle por acentos o mayúsculas.
   * @param {unknown} value Texto de entrada.
   * @returns {string} Texto normalizado para comparar.
   */
  const normalizeSearchText = (value) => String(value || '')
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '')
    .toLowerCase()
    .trim();

  /**
   * Convierte un agente recibido desde navegación en una opción seleccionable.
   * @param {Record<string, any>|null} target Agente recibido desde Mis agentes.
   * @returns {Record<string, any>|null} Opción compatible con el selector.
   */
  const optionFromTargetAgent = (target) => {
    if (!target) return null;
    const id = target.agentId || target.agentDbId || target.agent_id || target.agent?.agentDbId || '';
    const slug = target.agentSlug || target.slug || target.id || target.agent?.id || '';
    const name = target.agentName || target.name || target.agent?.name || '';
    if (!id || !name) return null;
    return {
      id,
      slug,
      name,
      tagline: target.tagline || target.agent?.tagline || 'Agente seleccionado para despliegue',
      source: 'selected',
    };
  };

  /**
   * Convierte despliegues existentes en opciones de agente para reutilizarlos.
   * @param {Array<Record<string, any>>} items Despliegues del usuario.
   * @returns {Array<Record<string, any>>} Agentes deduplicados desde despliegues.
   */
  const optionsFromDeployments = (items) => {
    const byId = new Map();
    (Array.isArray(items) ? items : []).forEach((deployment) => {
      const id = deployment.agent_id || deployment.agentId || deployment.agent?.id || '';
      const name = deployment.agent_name || deployment.agentName || deployment.agent?.name || '';
      if (!id || !name || byId.has(String(id))) return;
      byId.set(String(id), {
        id,
        slug: deployment.agent_slug || deployment.agentSlug || deployment.agent?.slug || '',
        name,
        tagline: `Ya desplegado · ${deployment.channel || 'canal activo'}`,
        source: 'deployed',
      });
    });
    return Array.from(byId.values());
  };

  React.useEffect(() => {
    loadRequests();
    loadDeployments();
    loadAgents();
  }, [loadRequests, loadDeployments, loadAgents]);

  React.useEffect(() => {
    try {
      const raw = sessionStorage.getItem('af_deploy_agent');
      if (!raw) return;
      sessionStorage.removeItem('af_deploy_agent');
      setPendingDeployAgent(JSON.parse(raw));
    } catch (_) {}
  }, []);

  const deploymentAgents = React.useMemo(() => optionsFromDeployments(deployments), [deployments]);

  const selectableAgents = React.useMemo(() => {
    const byId = new Map();
    const add = (agent) => {
      if (!agent?.id) return;
      const key = String(agent.id);
      const current = byId.get(key);
      byId.set(key, {
        ...current,
        ...agent,
        name: agent.name || current?.name || 'Agente',
        slug: agent.slug || current?.slug || '',
      });
    };
    agents.forEach(add);
    deploymentAgents.forEach(add);
    add(optionFromTargetAgent(preselectedAgent));
    add(optionFromTargetAgent(pendingDeployAgent));
    return Array.from(byId.values());
  }, [agents, deploymentAgents, preselectedAgent, pendingDeployAgent]);

  /**
   * Resuelve el agente elegido desde Mis agentes o desde el selector del modal.
   * @param {Record<string, any>|null} target Agente origen.
   * @returns {Record<string, any>|null} Agente resuelto con UUID real.
   */
  const resolveDeployTarget = React.useCallback((target) => {
    if (!target) return null;
    const targetOption = optionFromTargetAgent(target);
    const agentSlug = targetOption?.slug || target.agentSlug || target.id || target.agent?.id || '';
    const agentDbId = targetOption?.id || target.agentId || target.agentDbId || '';
    const agentName = targetOption?.name || target.agentName || target.agent?.name || target.name || 'seleccionado';
    const normalizedName = normalizeSearchText(agentName);
    const match = selectableAgents.find((a) =>
      String(a.id || '') === String(agentDbId || '') ||
      String(a.slug || '') === String(agentSlug || '') ||
      normalizeSearchText(a.name) === normalizedName
    );
    const resolvedId = agentDbId || match?.id || '';
    if (!resolvedId) return null;
    return {
      id: resolvedId,
      slug: agentSlug || match?.slug || '',
      name: agentName || match?.name || 'seleccionado',
    };
  }, [selectableAgents]);

  /**
   * Preselecciona en el formulario el agente que llegó desde Mis agentes.
   * @param {Record<string, any>|null} target
   * @param {boolean} openDrawer
   * @returns {boolean}
   */
  const prefillDeployAgent = React.useCallback((target, openDrawer = true) => {
    const resolved = resolveDeployTarget(target);
    if (!resolved) return false;
    const ctx = loadAgentContextBySlug(resolved.slug);
    const promptFromCtx = contextPromptFromForm(ctx);
    setDeployForm((f) => ({
      ...f,
      agent_id: resolved.id,
      system_prompt_override: promptFromCtx || f.system_prompt_override,
    }));
    if (openDrawer) {
      setShowNewDeploy(true);
    }
    return true;
  }, [resolveDeployTarget]);

  // Pre-seleccionar agente si venimos desde "Mis Agentes → Desplegar".
  React.useEffect(() => {
    const target = preselectedAgent || pendingDeployAgent;
    if (!target) return;
    const ok = prefillDeployAgent(target, true);
    if (ok) {
      const name = target.agentName || target.agent?.name || target.name || 'seleccionado';
      notify(`Agente "${name}" pre-cargado para despliegue`);
    }
  }, [preselectedAgent, pendingDeployAgent, selectableAgents, prefillDeployAgent]);

  /**
   * Registra una solicitud comercial de implantación.
   * @param {{source:string, mode_id?:string, mode_name?:string, note?:string}} payload
   * @returns {Promise<void>}
   */
  const submitDeploymentRequest = async (payload) => {
    const token = getToken();
    if (!token) {
      notify('Necesitas iniciar sesión');
      return;
    }
    setSendingRequest(true);
    try {
      const res = await fetch('/api/v1/deployment/requests', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Authorization: 'Bearer ' + token,
        },
        body: JSON.stringify(payload),
      });
      if (!res.ok) {
        notify('No se pudo registrar la solicitud');
        return;
      }
      const created = await res.json();
      setRequests((prev) => [created, ...prev]);
      notify('Solicitud comercial enviada');
    } catch (_) {
      notify('Error de red al enviar la solicitud');
    } finally {
      setSendingRequest(false);
    }
  };

  const modes = [
    {
      id: 'saas-multi', name: 'SaaS multi-tenant', tagline: 'Cloud compartido — empezar en minutos',
      icon: 'cloud', color: '#10b981', tier: 'starter',
      where: 'Nuestra nube (UE/EEUU)', tenancy: 'Compartida (aislada lógicamente)',
      time: 'Minutos', mgmt: 'Nosotros', price: 'Suscripción + consumo',
      desc: 'El cliente accede a AgentForge-IA en nuestra infraestructura cloud, compartiendo recursos con otros tenants. Datos siempre aislados lógicamente, cifrados en reposo y tránsito.',
      ideal: ['PYMEs y startups', 'Equipos que quieren empezar ya', 'Casos de uso estándar sin requisitos sectoriales fuertes'],
      includes: ['Self-serve desde la web', 'Actualizaciones automáticas', 'Región configurable (UE/EEUU)', 'Marketplace completo de agentes'],
      excludes: ['Aislamiento físico de cómputo', 'BYOK', 'Air-gap'],
      sla: '99,9 %', regions: ['UE-Madrid','UE-Frankfurt','EEUU-Este','EEUU-Oeste'],
      services: [
        { name: 'Hosting cloud (Azure/AWS)', cost: 'incluido' },
        { name: 'Backups diarios', cost: 'incluido' },
        { name: 'SSL/TLS y CDN', cost: 'incluido' },
        { name: 'Actualizaciones automáticas', cost: 'incluido' },
        { name: 'Onboarding remoto', cost: '490 €' },
      ],
    },
    {
      id: 'saas-dedicated', name: 'SaaS dedicado', tagline: 'Single-tenant en nuestra nube',
      icon: 'shield', color: '#3b82f6', tier: 'business',
      where: 'Nuestra nube (región fijada)', tenancy: 'Dedicada (single-tenant)',
      time: '1–2 días', mgmt: 'Nosotros (con ventanas)', price: 'Suscripción anual + cuota fija',
      desc: 'Instancia aislada del cliente — su propia base de datos, cómputo y dominio. Nosotros operamos pero el cliente decide cuándo aplicar actualizaciones.',
      ideal: ['Empresas medianas con cumplimiento RGPD reforzado', 'ENS medio', 'Sector financiero o salud sin restricciones de "no SaaS"'],
      includes: ['Dominio propio (empresa.agentforge.ai)','Copias de seguridad dedicadas','BYOK opcional','Ventanas de mantenimiento pactadas','Auditoría SOC 2 / ISO 27001'],
      excludes: ['Despliegue en nube del cliente','Operación air-gapped'],
      sla: '99,95 %', regions: ['UE-Madrid','UE-Frankfurt','UE-Irlanda'],
      services: [
        { name: 'Infraestructura dedicada (single-tenant)', cost: 'desde 180 €/mes' },
        { name: 'Dominio propio + SSL', cost: 'incluido' },
        { name: 'Copias de seguridad dedicadas', cost: 'incluido' },
        { name: 'Configuración inicial', cost: '890 €' },
        { name: 'Monitorización 24/7', cost: '120 €/mes' },
        { name: 'Soporte técnico prioritario', cost: '150 €/mes' },
      ],
    },
    {
      id: 'byoc', name: 'Private Cloud / BYOC', tagline: 'Despliegue en la nube del cliente',
      icon: 'lock', color: '#8b5cf6', tier: 'enterprise',
      where: 'Cuenta cloud del cliente', tenancy: 'Dedicada en su tenant',
      time: '1–3 semanas', mgmt: 'Compartido', price: 'Licencia anual + servicios profesionales',
      desc: 'Desplegamos AgentForge-IA en la cuenta cloud del cliente (AWS, Azure o GCP). Nosotros gestionamos el software vía un agente de despliegue; los datos y el cómputo nunca salen de su tenant.',
      ideal: ['Banca y seguros','Salud y farmacia','Sector público','Grandes corporaciones con políticas de "no SaaS"'],
      includes: ['Integración con KMS, IAM y VPC del cliente','Logs y métricas en su SIEM','Datos nunca salen de su tenant','Actualizaciones validadas por el cliente'],
      excludes: ['Operación 100 % offline (ver On-prem)'],
      sla: '99,95 %', regions: ['AWS · Azure · GCP — cualquier región del cliente'],
      services: [
        { name: 'Despliegue en tenant del cliente', cost: 'incluido en licencia' },
        { name: 'Configuración inicial (IaC + CI/CD)', cost: '1.490 €' },
        { name: 'Integración KMS/IAM/VPC', cost: '890 €' },
        { name: 'Monitorización 24/7', cost: '120 €/mes' },
        { name: 'Soporte técnico prioritario', cost: '150 €/mes' },
      ],
    },
    {
      id: 'on-prem', name: 'On-premise', tagline: 'Auto-hospedado en infraestructura propia',
      icon: 'server', color: '#f59e0b', tier: 'enterprise',
      where: 'Data center del cliente', tenancy: 'Total — sin dependencia externa',
      time: '3–8 semanas', mgmt: 'Cliente (con soporte)', price: 'Licencia anual o perpetua + soporte enterprise',
      desc: 'El cliente ejecuta AgentForge-IA en sus propios servidores. Entregamos contenedores firmados (Docker / Helm para Kubernetes). Compatible con operación air-gapped usando LLMs locales.',
      ideal: ['Defensa e infraestructuras críticas','Administración pública con datos clasificados','Sectores regulados sin acceso a cloud público'],
      includes: ['Contenedores firmados','Helm chart para Kubernetes','Soporte para LLMs locales (Llama, Mistral, modelos propios)','Modo air-gapped','Parches mensuales con changelog'],
      excludes: ['Marketplace de agentes en tiempo real (sólo paquetes offline)'],
      sla: 'Definido por el cliente', regions: ['Cualquiera — incluso sin internet'],
      services: [
        { name: 'Despliegue en infraestructura cliente', cost: 'consultar' },
        { name: 'Integración SSO/LDAP', cost: '1.200 €' },
        { name: 'Formación equipo técnico', cost: '800 €/día' },
        { name: 'SLA dedicado + soporte 24/7', cost: 'desde 400 €/mes' },
        { name: 'Parches y actualizaciones anuales', cost: 'incluido en licencia' },
      ],
    },
    {
      id: 'edge', name: 'Edge / híbrido', tagline: 'Datos en local, marketplace en cloud',
      icon: 'wifi', color: '#06b6d4', tier: 'business',
      where: 'On-prem o BYOC + servicios cloud', tenancy: 'Híbrida',
      time: '2–4 semanas', mgmt: 'Compartido', price: 'Licencia + suscripción a servicios cloud',
      desc: 'El orquestador y los datos sensibles viven on-prem o en BYOC; ciertos componentes (modelos grandes, marketplace) se consumen como servicio. Pensado para sitios distribuidos con baja latencia.',
      ideal: ['Retail con muchas tiendas','Industria y fábricas','Logística y centros distribuidos','Operación parcialmente offline'],
      includes: ['Sincronización con cloud central','Caché local de modelos','Failover offline','Despliegue por sucursal'],
      excludes: ['Air-gapped completo'],
      sla: '99,9 %', regions: ['Combinación cliente + nuestra nube'],
      services: [
        { name: 'Nodo edge por sucursal', cost: 'desde 80 €/mes' },
        { name: 'Configuración inicial (nodo + cloud)', cost: '1.190 €' },
        { name: 'Sincronización y caché de modelos', cost: 'incluido' },
        { name: 'Monitorización 24/7', cost: '120 €/mes' },
        { name: 'Soporte técnico prioritario', cost: '150 €/mes' },
      ],
    },
    {
      id: 'oem', name: 'White-label / OEM', tagline: 'Integra AgentForge-IA en tu producto',
      icon: 'package', color: '#ec4899', tier: 'partner',
      where: 'Cualquier infraestructura', tenancy: 'Según el partner',
      time: '4–8 semanas', mgmt: 'Partner', price: 'Revenue share o licencia por volumen',
      desc: 'Un partner integra AgentForge-IA en su propio producto bajo su marca, vía API y SDK. Branding, dominios y facturación al cliente final son del partner.',
      ideal: ['Consultoras e integradores','ISVs verticales (salud, legal, edu…)','Plataformas que quieren ofrecer agentes a sus clientes'],
      includes: ['API y SDKs (JS, Python, Go)','Branding personalizable (colores, logos, dominios)','Multi-tenant gestionado por el partner','Revenue share configurable'],
      excludes: ['UI estándar de AgentForge — el partner construye la suya'],
      sla: 'Según contrato del partner', regions: ['Sin restricción'],
      services: [
        { name: 'Integración API + SDK', cost: 'consultar' },
        { name: 'Branding y white-label setup', cost: '1.500 €' },
        { name: 'Formación equipo técnico partner', cost: '800 €/día' },
        { name: 'SLA dedicado + soporte 24/7', cost: 'desde 400 €/mes' },
        { name: 'Gestor de cuenta partner', cost: 'incluido en contrato' },
      ],
    },
  ];

  const tiers = { starter: { lbl: 'Starter', c: '#10b981' }, business: { lbl: 'Business', c: '#3b82f6' }, enterprise: { lbl: 'Enterprise', c: '#8b5cf6' }, partner: { lbl: 'Partner', c: '#ec4899' } };

  const filtered = filter === 'todas' ? modes : modes.filter(m => m.tier === filter);

  const services = [
    { name: 'Onboarding asistido', desc: 'Descubrimiento, conexión de fuentes y primeros 3–5 agentes en producción', dur: '1–4 semanas', icon: 'rocket' },
    { name: 'Migración', desc: 'Importación de datos, agentes y conversaciones desde otras plataformas', dur: '1–3 semanas', icon: 'upload' },
    { name: 'Formación', desc: 'Sesiones para administradores, builders y usuarios finales', dur: '8–24 horas', icon: 'book' },
    { name: 'Soporte', desc: 'Standard (8×5), Business (SLA 4h) o Enterprise (24×7, TAM dedicado)', dur: 'Continuo', icon: 'headset' },
    { name: 'Servicios profesionales', desc: 'Agentes a medida, integraciones bespoke, auditorías de seguridad', dur: 'Bajo demanda', icon: 'wrench' },
  ];

  const recommendMode = () => {
    const { sector, data, team, budget } = assistantAnswers;
    if (data === 'airgap') return modes.find((m) => m.id === 'on-prem') || modes[3];
    if (data === 'customer-cloud') return modes.find((m) => m.id === 'byoc') || modes[2];
    if (sector === 'partner') return modes.find((m) => m.id === 'oem') || modes[5];
    if (team === 'distributed') return modes.find((m) => m.id === 'edge') || modes[4];
    if (budget === 'starter') return modes.find((m) => m.id === 'saas-multi') || modes[0];
    return modes.find((m) => m.id === 'saas-dedicated') || modes[1];
  };

  const createDeployment = async () => {
    const token = getToken();
    setDeployFeedback(null);
    if (!token) {
      setDeployFeedback({ type: 'error', message: 'Necesitas iniciar sesión para crear el despliegue.' });
      notify('Necesitas iniciar sesión');
      return;
    }
    if (!deployForm.agent_id) {
      setDeployFeedback({ type: 'error', message: 'Selecciona un agente antes de crear el despliegue.' });
      notify('Selecciona un agente');
      return;
    }
    if (deployForm.channel === 'web' && !deployForm.allowed_origins.trim()) {
      setDeployFeedback({ type: 'error', message: 'Indica al menos un dominio permitido o * para pruebas.' });
      notify('Indica al menos un dominio permitido o * para pruebas');
      return;
    }
    if (
      deployForm.channel === 'whatsapp' &&
      (
        !deployForm.whatsapp_access_token.trim() ||
        !deployForm.whatsapp_phone_number_id.trim() ||
        !deployForm.whatsapp_verify_token.trim()
      )
    ) {
      setDeployFeedback({ type: 'error', message: 'WhatsApp Meta requiere Access Token, Phone Number ID y Verify Token.' });
      notify('Meta requiere Access Token, Phone Number ID y Verify Token');
      return;
    }
    if (deployForm.channel === 'telegram' && !deployForm.telegram_bot_token.trim()) {
      setDeployFeedback({ type: 'error', message: 'El token del bot de Telegram es obligatorio para Telegram.' });
      notify('El token del bot de Telegram es obligatorio para Telegram');
      return;
    }
    setDeployFormLoading(true);
    try {
      const body = { agent_id: deployForm.agent_id, channel: deployForm.channel };
      if (deployForm.allowed_origins) body.allowed_origins = deployForm.allowed_origins;
      if (deployForm.whatsapp_access_token) body.whatsapp_access_token = deployForm.whatsapp_access_token;
      if (deployForm.whatsapp_phone_number_id) body.whatsapp_phone_number_id = deployForm.whatsapp_phone_number_id;
      if (deployForm.whatsapp_verify_token) body.whatsapp_verify_token = deployForm.whatsapp_verify_token;
      if (deployForm.telegram_bot_token) body.telegram_bot_token = deployForm.telegram_bot_token;
      if (deployForm.system_prompt_override) body.system_prompt_override = deployForm.system_prompt_override;
      const res = await fetch('/api/v1/deployment/deploy', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token },
        body: JSON.stringify(body),
      });
      if (!res.ok) {
        let reason = `HTTP ${res.status}`;
        try {
          const payload = await res.json();
          reason = payload?.detail || payload?.message || reason;
        } catch (_) {}
        setDeployFeedback({ type: 'error', message: `No se pudo crear el despliegue: ${reason}` });
        notify('Error al crear el despliegue');
        return;
      }
      const created = await res.json();
      setDeployments((prev) => [created, ...prev]);
      notify(created.channel_configured ? 'Despliegue creado y configurado' : 'Despliegue creado: revisa configuración pendiente');
      setLastCreatedDeployment(created);
      setDeployFeedback({
        type: 'success',
        message: `Despliegue creado para ${created.agent_name} en ${created.channel}.`,
        deployment: created,
      });
    } catch (_) {
      setDeployFeedback({ type: 'error', message: 'Error de red al crear el despliegue. Revisa conexión o sesión.' });
      notify('Error de red');
    }
    finally { setDeployFormLoading(false); }
  };

  const deleteDeployment = async (id) => {
    const token = getToken();
    if (!token) return;
    try {
      const res = await fetch('/api/v1/deployment/deployments/' + id, {
        method: 'DELETE',
        headers: { Authorization: 'Bearer ' + token },
      });
      if (!res.ok) {
        let reason = `HTTP ${res.status}`;
        try {
          const payload = await res.json();
          reason = payload?.detail || payload?.message || reason;
        } catch (_) {}
        notify(`No se pudo eliminar el despliegue: ${reason}`);
        return;
      }
      setDeployments((prev) => prev.filter((d) => d.id !== id));
      notify('Despliegue eliminado');
    } catch (_) { notify('Error al eliminar'); }
  };

  const copyToClipboard = (text, key) => {
    navigator.clipboard.writeText(text).then(() => {
      setCopiedToken(key);
      setTimeout(() => setCopiedToken(null), 2000);
    });
  };

  const openNewDeployForChannel = (channel) => {
    const target = preselectedAgent || pendingDeployAgent;
    let nextAgentId = deployForm.agent_id;
    let nextPrompt = '';
    if (target) {
      const resolved = resolveDeployTarget(target);
      if (resolved) {
        const ctx = loadAgentContextBySlug(resolved.slug);
        nextAgentId = resolved.id;
        nextPrompt = contextPromptFromForm(ctx);
      }
    }
    setAgentSearch('');
    setDeployFeedback(null);
    setLastCreatedDeployment(null);
    setDeployForm({
      ...emptyDeployForm,
      agent_id: nextAgentId || '',
      channel,
      system_prompt_override: nextPrompt,
    });
    setShowNewDeploy(true);
  };

  const closeNewDeploy = () => {
    setShowNewDeploy(false);
    setDeployFeedback(null);
    setLastCreatedDeployment(null);
    setAgentSearch('');
    setDeployForm(emptyDeployForm);
  };

  const selectedAgentOption = React.useMemo(
    () => selectableAgents.find((a) => String(a.id) === String(deployForm.agent_id)) || null,
    [selectableAgents, deployForm.agent_id],
  );

  const filteredAgents = React.useMemo(() => {
    const q = normalizeSearchText(agentSearch);
    const matches = !q ? selectableAgents : selectableAgents.filter((a) => normalizeSearchText([
      a.name,
      a.slug,
      a.tagline,
      a.source === 'installed' ? 'instalado' : '',
      a.source === 'deployed' ? 'desplegado' : '',
      a.source === 'selected' ? 'seleccionado' : '',
    ].filter(Boolean).join(' ')).includes(q));
    if (!selectedAgentOption || matches.some((a) => String(a.id) === String(selectedAgentOption.id))) {
      return matches;
    }
    return [selectedAgentOption, ...matches];
  }, [selectableAgents, agentSearch, selectedAgentOption]);

  const activeChannels = [
    {
      id: 'web',
      name: 'Web del cliente',
      desc: 'Crea un snippet de widget para insertar el agente en la web del cliente.',
      color: '#38bdf8',
      icon: 'globe',
    },
    {
      id: 'whatsapp',
      name: 'WhatsApp Meta Oficial',
      desc: 'Crea webhook oficial de WhatsApp Cloud API y conecta el número del cliente en Meta.',
      color: '#4ade80',
      icon: 'send',
    },
    {
      id: 'telegram',
      name: 'Telegram',
      desc: 'Crea webhook y registra automáticamente el bot mediante Telegram Bot API.',
      color: '#c084fc',
      icon: 'chat',
    },
  ];

  const channelHelp = {
    web: {
      title: 'Web del cliente',
      text: 'Crea un token público y un snippet JavaScript. El widget vive en AgentForge y se inserta en la web del cliente.',
      result: 'Resultado: snippet + URL técnica de chat.',
    },
    whatsapp: {
      title: 'WhatsApp Cloud API (Meta oficial)',
      text: 'Conecta Access Token, Phone Number ID y Verify Token de Meta para operar con el canal oficial del cliente.',
      result: 'Resultado: webhook oficial de Meta + envío/recepción por Graph API.',
    },
    telegram: {
      title: 'Telegram',
      text: 'Crea un webhook público y lo registra en Telegram Bot API con el token del bot.',
      result: 'Resultado: webhook Telegram + bot conectado al agente.',
    },
  };

  const channelActionLabel = {
    web: 'Crear despliegue Web',
    whatsapp: 'Conectar Meta oficial',
    telegram: 'Conectar Telegram',
  };

  const addOnOptions = [
    { id: 'web-build', name: 'Web creada por AgentForge', desc: 'Diseño y entrega de web comercial con integración de agentes desde el día 1.' },
    { id: 'onboarding', name: 'Onboarding asistido', desc: 'Acompañamiento para configurar fuentes, pruebas y primeras respuestas reales.' },
    { id: 'training', name: 'Formación de equipo', desc: 'Sesión guiada para administradores, builders y usuarios finales.' },
  ];

  /**
   * Descarga una ficha técnica textual de la modalidad seleccionada.
   * @returns {void}
   */
  const downloadTechSheet = () => {
    const mode = modes.find((m) => m.id === selected) || null;
    if (!mode) {
      notify('Selecciona una modalidad para descargar su ficha');
      return;
    }
    const lines = [
      `AgentForge · Ficha técnica`,
      `Modalidad: ${mode.name}`,
      `Tagline: ${mode.tagline}`,
      ``,
      `Descripción:`,
      mode.desc,
      ``,
      `Datos clave:`,
      `- Dónde viven los datos: ${mode.where}`,
      `- Tenancy: ${mode.tenancy}`,
      `- Puesta en marcha: ${mode.time}`,
      `- Operación: ${mode.mgmt}`,
      `- SLA: ${mode.sla}`,
      `- Modelo de precio: ${mode.price}`,
      ``,
      `Incluye:`,
      ...mode.includes.map((v) => `- ${v}`),
      ``,
      `No incluye:`,
      ...mode.excludes.map((v) => `- ${v}`),
      ``,
      `Regiones: ${mode.regions.join(', ')}`,
    ];
    const blob = new Blob([lines.join('\n')], { type: 'text/plain;charset=utf-8;' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `ficha-${mode.id}.txt`;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
    notify(`Descarga iniciada: ${mode.name}`);
  };

  return (
    <div>
      <div className="page-head">
        <h1>Implantación</h1>
        <span className="crumbs mono">contratación <span>›</span> <span style={{ color: 'var(--ink)' }}>modalidades de despliegue</span></span>
        <div className="actions">
          <button className="btn" onClick={() => setShowPdf(true)}><Icon name="download" size={12} /> Ficha PDF</button>
          <button className="btn" onClick={() => setShowSales(true)}><Icon name="users" size={12} /> Hablar con ventas</button>
          <button className="btn btn-primary" onClick={() => setShowCompare(true)}><Icon name="zap" size={13} /> Comparar modalidades</button>
          <button className="btn btn-primary" onClick={() => setShowNewDeploy(true)}><Icon name="plus" size={13} /> Nuevo despliegue</button>
        </div>
      </div>

      {/* Mis Despliegues */}
      <div className="section" style={{ marginBottom: 0 }}>
        <div className="section-title">
          <span>Canales operativos de despliegue</span>
          <span className="line" />
        </div>
        <div className="card" style={{ padding: 12, marginBottom: 10, borderColor: 'var(--line-2)', background: 'var(--bg-2)' }}>
          <div style={{ fontSize: 13, color: 'var(--ink-2)' }}>
            Estos son los canales que crean un despliegue real para cualquier agente seleccionado. Las modalidades de abajo son modelos de implantación comercial y arquitectura.
          </div>
        </div>
        {lastCreatedDeployment && (
          <div className="card" style={{ padding: 14, marginBottom: 12, borderColor: 'rgba(16,185,129,0.42)', background: 'linear-gradient(135deg, rgba(16,185,129,0.12), rgba(255,91,31,0.05))' }}>
            <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
              <span style={{ width: 34, height: 34, borderRadius: 10, display: 'grid', placeItems: 'center', background: 'rgba(16,185,129,0.16)', color: '#34d399', border: '1px solid rgba(16,185,129,0.35)' }}>
                <Icon name="check" size={16} />
              </span>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontWeight: 800, fontSize: 14, marginBottom: 4 }}>
                  Despliegue creado correctamente
                </div>
                <div style={{ color: 'var(--ink-2)', fontSize: 12.5, marginBottom: 10 }}>
                  {lastCreatedDeployment.agent_name} · {lastCreatedDeployment.channel} · estado {lastCreatedDeployment.status}
                </div>
                <div className="mono" style={{ fontSize: 11, color: 'var(--ink-3)', display: 'grid', gap: 5 }}>
                  {lastCreatedDeployment.webhook_url && <div>Webhook: {lastCreatedDeployment.webhook_url}</div>}
                  {lastCreatedDeployment.chat_url && <div>Chat URL: {lastCreatedDeployment.chat_url}</div>}
                  {lastCreatedDeployment.embed_snippet && <div>Snippet: {lastCreatedDeployment.embed_snippet}</div>}
                </div>
              </div>
              <button className="btn btn-sm" onClick={() => setLastCreatedDeployment(null)}>
                Cerrar
              </button>
            </div>
          </div>
        )}
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 10, marginBottom: 14 }}>
          {activeChannels.map((channel) => (
            <div key={channel.id} className="card" style={{ padding: 12, borderColor: `${channel.color}33` }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
                <span style={{ width: 28, height: 28, borderRadius: 8, display: 'grid', placeItems: 'center', background: `${channel.color}1a`, color: channel.color, border: `1px solid ${channel.color}55` }}>
                  <Icon name={channel.icon} size={13} />
                </span>
                <span style={{ fontWeight: 700, fontSize: 13 }}>{channel.name}</span>
                <span className="tier-pill" style={{ marginLeft: 'auto', color: channel.color, borderColor: `${channel.color}55`, background: `${channel.color}14` }}>Operativo</span>
              </div>
              <div style={{ color: 'var(--ink-3)', fontSize: 12, minHeight: 34 }}>{channel.desc}</div>
              <button className="btn btn-sm btn-primary" style={{ marginTop: 10 }} onClick={() => openNewDeployForChannel(channel.id)}>
                Desplegar en {channel.name}
              </button>
            </div>
          ))}
        </div>

        <div className="section-title">
          <span>Mis Despliegues</span>
          {deployments.length > 0 && <span className="badge" style={{ background: 'var(--brand)', color: '#fff', borderRadius: 6, padding: '1px 7px', fontSize: 11, fontWeight: 700 }}>{deployments.length}</span>}
          <span className="line" />
          <button className="btn btn-sm" onClick={() => window.dispatchEvent(new CustomEvent('af:navigate', { detail: 'library' }))} style={{ marginLeft: 8 }}>
            <Icon name="library" size={11} /> Ir a Mis agentes
          </button>
          <button className="btn btn-sm" onClick={() => setShowDeploymentsList((v) => !v)} style={{ marginLeft: 8 }}>
            <Icon name="chevron-down" size={11} /> {showDeploymentsList ? 'Ocultar lista' : 'Ver lista'}
          </button>
          <button className="btn btn-sm btn-primary" onClick={() => setShowNewDeploy(true)} style={{ marginLeft: 8 }}><Icon name="plus" size={11} /> Nuevo despliegue</button>
        </div>

        {!showDeploymentsList ? (
          <div className="card" style={{ padding: '12px 14px', color: 'var(--ink-3)', fontSize: 13 }}>
            Lista de despliegues oculta para priorizar las modalidades de implantación. Puedes gestionarlos desde <b>Mis agentes</b> o abrir la lista aquí cuando lo necesites.
          </div>
        ) : deployments.length === 0 ? (
          <div className="card" style={{ padding: '16px', color: 'var(--ink-3)', fontSize: 13 }}>
            No tienes despliegues activos. Crea uno para incrustar un agente en tu web, WhatsApp o Telegram.
          </div>
        ) : (
          <div style={{ display: 'grid', gap: 10 }}>
            {deployments.map((d) => (
              <div key={d.id} className="card" style={{ padding: 14 }}>
                <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
                      <span style={{ fontWeight: 600, fontSize: 14 }}>{d.agent_name}</span>
                      <span style={{ fontSize: 11, fontWeight: 600, padding: '2px 7px', borderRadius: 5,
                        background: d.channel === 'web' ? '#0ea5e91a' : d.channel.startsWith('whatsapp') ? '#22c55e1a' : '#a855f71a',
                        color: d.channel === 'web' ? '#38bdf8' : d.channel.startsWith('whatsapp') ? '#4ade80' : '#c084fc'
                      }}>{d.channel}</span>
                      <span style={{ fontSize: 11, color: 'var(--ink-3)', fontFamily: 'var(--mono)' }}>#{d.id.slice(0,8)}</span>
                    </div>
                    <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 8 }}>
                      <span style={{ fontSize: 11, padding: '2px 7px', borderRadius: 999, background: d.channel_configured ? '#22c55e1a' : '#f59e0b1a', color: d.channel_configured ? '#4ade80' : '#fbbf24' }}>
                        {d.channel_configured ? 'Configuración OK' : 'Config pendiente'}
                      </span>
                      <span style={{ fontSize: 11, padding: '2px 7px', borderRadius: 999, background: d.status === 'active' ? '#3b82f61a' : '#64748b1a', color: d.status === 'active' ? '#60a5fa' : '#94a3b8' }}>
                        {d.status === 'active' ? 'Activo' : d.status}
                      </span>
                      <span style={{ fontSize: 11, padding: '2px 7px', borderRadius: 999, background: '#0ea5e91a', color: '#38bdf8' }}>
                        {d.channel === 'web'
                          ? 'Web del cliente'
                          : d.channel === 'whatsapp'
                            ? 'WhatsApp Meta'
                            : 'Telegram'}
                      </span>
                    </div>
                    {d.channel_config_note && (
                      <div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginBottom: 8 }}>
                        {d.channel_config_note}
                      </div>
                    )}
                    <div style={{ marginBottom: 8, background: 'var(--bg-2)', border: '1px solid var(--line)', borderRadius: 8, padding: 8 }}>
                      <div className="mono" style={{ fontSize: 10.5, color: 'var(--ink-3)', marginBottom: 6 }}>CHECKLIST DESPLIEGUE</div>
                      {(() => {
                        const checks = [
                          { ok: !!d.channel_configured, label: 'Configuración del canal' },
                          { ok: d.channel === 'web' ? !!d.embed_snippet : !!d.webhook_url, label: d.channel === 'web' ? 'Snippet generado' : 'Webhook generado' },
                          { ok: d.status === 'active', label: 'Despliegue activo' },
                        ];
                        return (
                          <div style={{ display: 'grid', gap: 4 }}>
                            {checks.map((c, idx) => (
                              <div key={idx} style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12, color: c.ok ? '#4ade80' : '#fbbf24' }}>
                                <Icon name={c.ok ? 'check' : 'clock'} size={11} />
                                <span style={{ color: 'var(--ink-2)' }}>{c.label}</span>
                              </div>
                            ))}
                          </div>
                        );
                      })()}
                    </div>
                    {d.embed_snippet && (
                      <div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
                        <code style={{ flex: 1, fontSize: 11, background: 'var(--bg-2)', border: '1px solid var(--line)', borderRadius: 6, padding: '5px 8px', color: 'var(--ink-2)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', display: 'block' }}>{d.embed_snippet}</code>
                        <button className="btn btn-sm" onClick={() => copyToClipboard(d.embed_snippet, d.id + '_embed')} style={{ flexShrink: 0 }}>
                          {copiedToken === d.id + '_embed' ? '✓' : <Icon name="copy" size={11} />}
                        </button>
                      </div>
                    )}
                    {d.webhook_url && (
                      <div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
                        <code style={{ flex: 1, fontSize: 11, background: 'var(--bg-2)', border: '1px solid var(--line)', borderRadius: 6, padding: '5px 8px', color: 'var(--ink-2)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', display: 'block' }}>{d.webhook_url}</code>
                        <button className="btn btn-sm" onClick={() => copyToClipboard(d.webhook_url, d.id + '_webhook')} style={{ flexShrink: 0 }}>
                          {copiedToken === d.id + '_webhook' ? '✓' : <Icon name="copy" size={11} />}
                        </button>
                      </div>
                    )}
                  </div>
                  <button className="btn btn-sm" style={{ color: '#f87171', borderColor: '#f871711a', background: '#f871710d', flexShrink: 0 }} onClick={() => deleteDeployment(d.id)}>
                    <Icon name="trash" size={11} />
                  </button>
                </div>
              </div>
            ))}
          </div>
        )}
      </div>

      <div className="section">
        <div className="card" style={{ padding: 18, marginBottom: 18, background: 'linear-gradient(135deg, var(--brand-soft), transparent 60%)' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
            <div style={{ width: 44, height: 44, borderRadius: 12, background: 'var(--brand)', color: '#1a0a04', display: 'grid', placeItems: 'center', flexShrink: 0 }}><Icon name="forge" size={20} /></div>
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 15, fontWeight: 600 }}>Modalidades de implantación comercial</div>
              <div style={{ fontSize: 13, color: 'var(--ink-2)', marginTop: 2 }}>Estas opciones definen dónde vive la plataforma, quién opera la infraestructura y qué servicios acompañan el proyecto.</div>
            </div>
            <button className="btn btn-primary" onClick={() => setShowAssistant(true)}><Icon name="sparkles" size={13} /> Asistente de implantación</button>
          </div>
        </div>

        <div className="crm-toolbar" style={{ marginBottom: 14 }}>
          <span className="mono" style={{ fontSize: 11, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.08em' }}>Filtrar por nivel</span>
          {['todas', 'starter', 'business', 'enterprise', 'partner'].map(t => (
            <button key={t} className={'crm-pill' + (filter === t ? ' active' : '')} onClick={() => setFilter(t)}>
              {t !== 'todas' && <span className="dot" style={{ background: tiers[t].c }} />}
              {t === 'todas' ? 'Todas' : tiers[t].lbl}
            </button>
          ))}
          <span style={{ marginLeft: 'auto', fontFamily: 'var(--mono)', fontSize: 11, color: 'var(--ink-3)' }}>{filtered.length} modalidades</span>
        </div>

        <div className="dep-grid">
          {filtered.map(m => (
            <div key={m.id} className={'dep-card' + (selected === m.id ? ' active' : '')} onClick={() => setSelected(selected === m.id ? null : m.id)}>
              <div className="dep-card-head">
                <div className="dep-icon" style={{ background: `${m.color}1a`, color: m.color, borderColor: `${m.color}55` }}><Icon name={m.icon} size={18} /></div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div className="dep-name">{m.name}</div>
                  <div className="dep-tag">{m.tagline}</div>
                </div>
                <span className="tier-pill" style={{ color: tiers[m.tier].c, borderColor: `${tiers[m.tier].c}55`, background: `${tiers[m.tier].c}1a` }}>{tiers[m.tier].lbl}</span>
              </div>
              <div style={{ display: 'inline-flex', alignItems: 'center', gap: 6, marginBottom: 10, padding: '3px 8px', borderRadius: 999, border: '1px solid var(--line)', background: 'var(--bg-2)', color: 'var(--ink-3)', fontSize: 11 }}>
                <Icon name="users" size={11} /> Solicitud comercial
              </div>
              <p className="dep-desc">{m.desc}</p>
              <div className="dep-stats">
                <div><span className="lbl">Dónde viven los datos</span><span className="val">{m.where}</span></div>
                <div><span className="lbl">Tenancy</span><span className="val">{m.tenancy}</span></div>
                <div><span className="lbl">Tiempo de puesta en marcha</span><span className="val" style={{ color: m.color }}>{m.time}</span></div>
                <div><span className="lbl">Quién opera</span><span className="val">{m.mgmt}</span></div>
                <div><span className="lbl">SLA</span><span className="val mono">{m.sla}</span></div>
                <div><span className="lbl">Modelo de precio</span><span className="val">{m.price}</span></div>
              </div>
              {m.services && m.services.length > 0 && (
                <div style={{ marginTop: 12, paddingTop: 12, borderTop: '1px solid var(--border)' }}>
                  <div style={{ fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.05em', color: 'var(--ink-3)', marginBottom: 8 }}>Servicios incluidos</div>
                  {m.services.map((s, idx) => (
                    <div key={idx} style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12, color: 'var(--ink-2)', padding: '4px 0', borderBottom: idx < m.services.length - 1 ? '1px solid var(--bg-3)' : 'none' }}>
                      <span>{s.name}</span>
                      <span style={{ fontWeight: 600, color: s.cost === 'incluido' || s.cost === 'incluido en licencia' || s.cost === 'incluido en contrato' ? 'var(--ok)' : 'var(--ink)' }}>{s.cost}</span>
                    </div>
                  ))}
                </div>
              )}
              {selected === m.id && (
                <div className="dep-expand">
                  <div className="dep-col">
                    <div className="dep-h">Ideal para</div>
                    <ul>{m.ideal.map((x,i) => <li key={i}><Icon name="check" size={11} style={{ color: 'var(--ok)' }} /> {x}</li>)}</ul>
                  </div>
                  <div className="dep-col">
                    <div className="dep-h">Incluye</div>
                    <ul>{m.includes.map((x,i) => <li key={i}><Icon name="check" size={11} style={{ color: m.color }} /> {x}</li>)}</ul>
                  </div>
                  <div className="dep-col">
                    <div className="dep-h">No incluye</div>
                    <ul>{m.excludes.map((x,i) => <li key={i}><Icon name="x" size={11} style={{ color: 'var(--ink-4)' }} /> {x}</li>)}</ul>
                    <div className="dep-h" style={{ marginTop: 12 }}>Regiones</div>
                    <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
                      {m.regions.map(r => <span key={r} className="tag-chip">{r}</span>)}
                    </div>
                  </div>
                </div>
              )}
              <div className="dep-foot">
                <button className="btn btn-sm" onClick={() => { setSelected(m.id); setShowPdf(true); }}><Icon name="book" size={11} /> Ficha técnica</button>
                <button
                  className="btn btn-sm btn-primary"
                  onClick={async () => {
                    setSelected(m.id);
                    await submitDeploymentRequest({
                      source: 'mode_cta',
                      mode_id: m.id,
                      mode_name: m.name,
                      note: `Interés directo en modalidad ${m.name}`,
                    });
                    setShowSales(true);
                  }}
                  disabled={sendingRequest}
                >
                  <Icon name="arrow-right" size={11} /> Solicitar estudio
                </button>
              </div>
            </div>
          ))}
        </div>

        <div className="ttl mono" style={{ fontSize: 10.5, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.08em', margin: '28px 0 12px', display: 'flex', alignItems: 'center', gap: 10 }}>
          <span>Servicios que acompañan toda implantación</span>
          <span style={{ flex: 1, height: 1, background: 'var(--line)' }} />
        </div>
        <div className="dep-svc-grid">
          {services.map(s => (
            <div key={s.name} className="dep-svc">
              <div className="dep-svc-icon"><Icon name={s.icon} size={16} /></div>
              <div style={{ flex: 1 }}>
                <div className="dep-svc-name">{s.name}</div>
                <div className="dep-svc-desc">{s.desc}</div>
                <div className="dep-svc-dur mono">{s.dur}</div>
              </div>
            </div>
          ))}
        </div>

        <div className="ttl mono" style={{ fontSize: 10.5, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.08em', margin: '28px 0 12px', display: 'flex', alignItems: 'center', gap: 10 }}>
          <span>Opciones adicionales de implantación</span>
          <span style={{ flex: 1, height: 1, background: 'var(--line)' }} />
        </div>
        <div className="dep-svc-grid">
          {addOnOptions.map((opt) => (
            <div key={opt.id} className="dep-svc">
              <div className="dep-svc-icon"><Icon name="plus" size={16} /></div>
              <div style={{ flex: 1 }}>
                <div className="dep-svc-name">{opt.name}</div>
                <div className="dep-svc-desc">{opt.desc}</div>
              </div>
              <button
                className="btn btn-sm btn-primary"
                onClick={async () => {
                  await submitDeploymentRequest({
                    source: 'addon',
                    mode_id: opt.id,
                    mode_name: opt.name,
                    note: `Solicitud adicional: ${opt.name}`,
                  });
                  setShowSales(true);
                }}
                disabled={sendingRequest}
              >
                Solicitar
              </button>
            </div>
          ))}
        </div>

        <div className="section-title" style={{ marginTop: 28 }}>
          <span>Solicitudes comerciales</span><span className="line" />
        </div>
        <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
          <table className="lib-table">
            <thead>
              <tr>
                <th style={{ paddingLeft: 16 }}>ID</th>
                <th>Origen</th>
                <th>Modalidad</th>
                <th>Nota</th>
                <th style={{ paddingRight: 16 }}>Fecha</th>
              </tr>
            </thead>
            <tbody>
              {requests.length === 0 && (
                <tr><td colSpan={5} style={{ padding: '12px 16px', color: 'var(--ink-3)' }}>Todavía no hay solicitudes.</td></tr>
              )}
              {requests.map((r) => (
                <tr key={r.id}>
                  <td style={{ paddingLeft: 16 }} className="mono">{String(r.id).slice(0, 8)}</td>
                  <td className="mono">{r.source || 'deployment_page'}</td>
                  <td>{r.mode_name || r.mode_id || 'General'}</td>
                  <td style={{ maxWidth: 420, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{r.note || '—'}</td>
                  <td style={{ paddingRight: 16 }} className="mono">{new Date(r.created_at).toLocaleString('es-ES')}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>

      {showAssistant && (
        <div className="drawer-back" onClick={() => setShowAssistant(false)}>
          <div className="drawer" style={{ width: 560 }} onClick={(e) => e.stopPropagation()}>
            <div className="drawer-head">
              <h2 style={{ margin: 0, fontSize: 18 }}>Asistente de implantación</h2>
              <button className="btn btn-ghost btn-icon" onClick={() => setShowAssistant(false)}><Icon name="x" size={14} /></button>
            </div>
            <div className="drawer-body" style={{ display: 'grid', gap: 14 }}>
              <label className="mono" style={{ fontSize: 11 }}>Sector
                <select className="input" value={assistantAnswers.sector} onChange={(e) => setAssistantAnswers((a) => ({ ...a, sector: e.target.value }))}>
                  <option value="general">General</option>
                  <option value="regulated">Regulado</option>
                  <option value="partner">Partner/OEM</option>
                </select>
              </label>
              <label className="mono" style={{ fontSize: 11 }}>Dónde deben vivir los datos
                <select className="input" value={assistantAnswers.data} onChange={(e) => setAssistantAnswers((a) => ({ ...a, data: e.target.value }))}>
                  <option value="standard">Nube AgentForge</option>
                  <option value="customer-cloud">Nube del cliente</option>
                  <option value="airgap">On-prem / air-gapped</option>
                </select>
              </label>
              <label className="mono" style={{ fontSize: 11 }}>Equipo
                <select className="input" value={assistantAnswers.team} onChange={(e) => setAssistantAnswers((a) => ({ ...a, team: e.target.value }))}>
                  <option value="small">Pequeño</option>
                  <option value="medium">Medio</option>
                  <option value="distributed">Distribuido</option>
                </select>
              </label>
              <label className="mono" style={{ fontSize: 11 }}>Presupuesto
                <select className="input" value={assistantAnswers.budget} onChange={(e) => setAssistantAnswers((a) => ({ ...a, budget: e.target.value }))}>
                  <option value="starter">Starter</option>
                  <option value="business">Business</option>
                  <option value="enterprise">Enterprise</option>
                </select>
              </label>

              <div className="card" style={{ padding: 12, border: '1px solid var(--line-2)' }}>
                {(() => {
                  const rec = recommendMode();
                  return (
                    <>
                      <div className="mono" style={{ fontSize: 10, color: 'var(--ink-3)' }}>Recomendación</div>
                      <div style={{ fontSize: 16, fontWeight: 700, marginTop: 4 }}>{rec.name}</div>
                      <div style={{ color: 'var(--ink-2)', marginTop: 4 }}>{rec.tagline}</div>
                    </>
                  );
                })()}
              </div>
            </div>
            <div className="drawer-foot">
              <button className="btn" onClick={() => setShowAssistant(false)}>Cerrar</button>
              <button
                className="btn btn-primary"
                onClick={async () => {
                  const rec = recommendMode();
                  setSelected(rec.id);
                  setShowAssistant(false);
                  notify(`Recomendación aplicada: ${rec.name}`);
                  await submitDeploymentRequest({
                    source: 'assistant_recommendation',
                    mode_id: rec.id,
                    mode_name: rec.name,
                    note: 'Solicitud generada desde asistente de implantación',
                  });
                }}
                disabled={sendingRequest}
              >
                Aplicar recomendación
              </button>
            </div>
          </div>
        </div>
      )}

      {showCompare && (
        <div className="drawer-back" onClick={() => setShowCompare(false)}>
          <div className="drawer" style={{ width: 720 }} onClick={(e) => e.stopPropagation()}>
            <div className="drawer-head">
              <h2 style={{ margin: 0, fontSize: 18 }}>Comparador de modalidades</h2>
              <button className="btn btn-ghost btn-icon" onClick={() => setShowCompare(false)}><Icon name="x" size={14} /></button>
            </div>
            <div className="drawer-body">
              <table className="lib-table">
                <thead><tr><th>Modalidad</th><th>Tiempo</th><th>SLA</th><th>Operación</th><th>Precio</th></tr></thead>
                <tbody>
                  {modes.map((m) => (
                    <tr key={m.id}>
                      <td>{m.name}</td><td>{m.time}</td><td>{m.sla}</td><td>{m.mgmt}</td><td>{m.price}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
            <div className="drawer-foot">
              <button className="btn" onClick={() => setShowCompare(false)}>Cerrar</button>
            </div>
          </div>
        </div>
      )}

      {showSales && (
        <div className="drawer-back" onClick={() => setShowSales(false)}>
          <div className="drawer" style={{ width: 560 }} onClick={(e) => e.stopPropagation()}>
            <div className="drawer-head">
              <h2 style={{ margin: 0, fontSize: 18 }}>Solicitud comercial creada</h2>
              <button className="btn btn-ghost btn-icon" onClick={() => setShowSales(false)}><Icon name="x" size={14} /></button>
            </div>
            <div className="drawer-body">
              <p style={{ color: 'var(--ink-2)' }}>
                Hemos registrado tu solicitud de implantación{selected ? ` para ${modes.find((m) => m.id === selected)?.name || 'la modalidad seleccionada'}` : ''}.
                El equipo contactará contigo para definir alcance, cronograma y presupuesto.
              </p>
            </div>
            <div className="drawer-foot">
              <button
                className="btn btn-primary"
                onClick={async () => {
                  if (!selected) {
                    await submitDeploymentRequest({
                      source: 'sales_general',
                      note: 'Contacto comercial general desde pantalla de implantación',
                    });
                  }
                  setShowSales(false);
                }}
                disabled={sendingRequest}
              >
                {sendingRequest ? 'Enviando...' : 'Aceptar'}
              </button>
            </div>
          </div>
        </div>
      )}

      {showPdf && (
        <div className="drawer-back" onClick={() => setShowPdf(false)}>
          <div className="drawer" style={{ width: 520 }} onClick={(e) => e.stopPropagation()}>
            <div className="drawer-head">
              <h2 style={{ margin: 0, fontSize: 18 }}>Ficha técnica</h2>
              <button className="btn btn-ghost btn-icon" onClick={() => setShowPdf(false)}><Icon name="x" size={14} /></button>
            </div>
            <div className="drawer-body">
              <p style={{ color: 'var(--ink-2)' }}>
                {selected
                  ? `Ficha técnica de ${modes.find((m) => m.id === selected)?.name || 'modalidad seleccionada'} lista para descarga.`
                  : 'Selecciona una modalidad para ver su ficha técnica detallada.'}
              </p>
            </div>
            <div className="drawer-foot">
              <button className="btn" onClick={() => setShowPdf(false)}>Cerrar</button>
              <button className="btn btn-primary" onClick={downloadTechSheet}><Icon name="download" size={12} /> Descargar</button>
            </div>
          </div>
        </div>
      )}

      {showNewDeploy && (
        <div className="drawer-back" onClick={closeNewDeploy}>
          <div className="drawer" style={{ width: 520 }} onClick={(e) => e.stopPropagation()}>
            <div className="drawer-head">
              <h2 style={{ margin: 0, fontSize: 18 }}>Nuevo despliegue</h2>
              <button className="btn btn-ghost btn-icon" onClick={closeNewDeploy}><Icon name="x" size={14} /></button>
            </div>
            <div className="drawer-body" style={{ display: 'grid', gap: 14 }}>
              <label className="mono" style={{ fontSize: 11 }}>Agente *
                <input
                  className="input"
                  name="af-deployment-agent-filter"
                  autoComplete="off"
                  autoCorrect="off"
                  spellCheck={false}
                  placeholder="Filtrar por nombre, slug o estado instalado..."
                  value={agentSearch}
                  onChange={(e) => setAgentSearch(e.target.value)}
                  style={{ margin: '6px 0 8px' }}
                />
                <select
                  className="input"
                  value={deployForm.agent_id}
                  onChange={(e) => {
                    const id = e.target.value;
                    const selectedAgent = selectableAgents.find((a) => a.id === id);
                    const ctx = loadAgentContextBySlug(selectedAgent?.slug || '');
                    const promptFromCtx = contextPromptFromForm(ctx);
                    setDeployForm((f) => ({
                      ...f,
                      agent_id: id,
                      system_prompt_override: promptFromCtx || f.system_prompt_override,
                    }));
                  }}
                >
                  <option value="">-- Selecciona un agente --</option>
                  {filteredAgents.map((a) => (
                    <option key={a.id} value={a.id}>
                      {a.name}
                      {a.source === 'installed' ? ' · instalado' : ''}
                      {a.source === 'deployed' ? ' · ya desplegado' : ''}
                      {a.source === 'selected' ? ' · seleccionado' : ''}
                    </option>
                  ))}
                </select>
                {deployForm.agent_id && (
                  <div style={{ marginTop: 6, color: 'var(--brand)', fontSize: 11 }}>
                    Agente seleccionado: {selectedAgentOption?.name || deployForm.agent_id}
                  </div>
                )}
              </label>
              {deployFeedback && (
                <div
                  className="card"
                  style={{
                    padding: 12,
                    borderColor: deployFeedback.type === 'success' ? 'rgba(16,185,129,0.42)' : 'rgba(248,113,113,0.48)',
                    background: deployFeedback.type === 'success' ? 'rgba(16,185,129,0.10)' : 'rgba(127,29,29,0.20)',
                  }}
                >
                  <div style={{ fontWeight: 800, color: deployFeedback.type === 'success' ? '#34d399' : '#fca5a5', marginBottom: 6 }}>
                    {deployFeedback.type === 'success' ? 'Despliegue creado correctamente' : 'No se pudo crear el despliegue'}
                  </div>
                  <div style={{ color: 'var(--ink-2)', fontSize: 12.3, lineHeight: 1.5 }}>
                    {deployFeedback.message}
                  </div>
                  {deployFeedback.deployment?.webhook_url && (
                    <div className="mono" style={{ marginTop: 8, fontSize: 10.8, color: 'var(--ink-3)', wordBreak: 'break-all' }}>
                      Webhook: {deployFeedback.deployment.webhook_url}
                    </div>
                  )}
                  {deployFeedback.deployment?.channel === 'whatsapp' && deployFeedback.deployment?.webhook_url && (
                    <div style={{ marginTop: 10, padding: 10, borderRadius: 12, border: '1px solid rgba(74,222,128,0.22)', background: 'rgba(34,197,94,0.07)', color: 'var(--ink-2)', fontSize: 11.8, lineHeight: 1.55 }}>
                      <strong style={{ color: '#34d399' }}>Siguiente paso en Meta:</strong> pega este webhook como Callback URL en tu App de Meta, usa el mismo Verify Token indicado en el formulario y suscribe el evento <strong>messages</strong>.
                    </div>
                  )}
                  {deployFeedback.deployment?.embed_snippet && (
                    <div className="mono" style={{ marginTop: 8, fontSize: 10.8, color: 'var(--ink-3)', wordBreak: 'break-all' }}>
                      Snippet: {deployFeedback.deployment.embed_snippet}
                    </div>
                  )}
                </div>
              )}
              <div className="card" style={{ padding: 12, border: '1px solid var(--line)', background: 'linear-gradient(135deg, rgba(255,91,31,0.08), var(--bg-2))' }}>
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, marginBottom: 9 }}>
                  <div>
                    <div className="mono" style={{ fontSize: 10.5, color: 'var(--ink-3)' }}>MODOS ACTIVOS DE DESPLIEGUE</div>
                    <div style={{ fontSize: 12.2, color: 'var(--ink-2)', marginTop: 4 }}>
                      Elige dónde vivirá la conversación del cliente. El agente se guarda en AgentForge.
                    </div>
                  </div>
                  <span className="tag" style={{ whiteSpace: 'nowrap' }}>Web · WhatsApp Meta · Telegram</span>
                </div>
                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 8 }}>
                  <button
                    type="button"
                    className={'btn btn-sm ' + (deployForm.channel === 'web' ? 'btn-primary' : 'btn-ghost')}
                    style={{ justifyContent: 'center', minHeight: 42 }}
                    onClick={() => setDeployForm((f) => ({ ...f, channel: 'web' }))}
                  >
                    <Icon name="globe" size={12} /> Web del cliente
                  </button>
                  <button
                    type="button"
                    className={'btn btn-sm ' + (deployForm.channel === 'whatsapp' ? 'btn-primary' : 'btn-ghost')}
                    style={{ justifyContent: 'center', minHeight: 42 }}
                    onClick={() => setDeployForm((f) => ({ ...f, channel: 'whatsapp' }))}
                  >
                    <Icon name="send" size={12} /> WhatsApp Meta
                  </button>
                  <button
                    type="button"
                    className={'btn btn-sm ' + (deployForm.channel === 'telegram' ? 'btn-primary' : 'btn-ghost')}
                    style={{ justifyContent: 'center', minHeight: 42 }}
                    onClick={() => setDeployForm((f) => ({ ...f, channel: 'telegram' }))}
                  >
                    <Icon name="chat" size={12} /> Telegram
                  </button>
                </div>
              </div>
              <div className="card" style={{ padding: 10, borderColor: 'var(--line)', background: 'var(--bg-2)' }}>
                <div style={{ fontWeight: 700, fontSize: 12.5, marginBottom: 4 }}>{channelHelp[deployForm.channel]?.title}</div>
                <div style={{ color: 'var(--ink-2)', fontSize: 12 }}>{channelHelp[deployForm.channel]?.text}</div>
                <div className="mono" style={{ color: 'var(--brand)', fontSize: 10.5, marginTop: 6 }}>{channelHelp[deployForm.channel]?.result}</div>
              </div>
              {deployForm.channel === 'whatsapp' && (
                <div style={{ display: 'grid', gap: 10 }}>
                  <div className="card" style={{ padding: 12, border: '1px solid rgba(74,222,128,0.24)', background: 'rgba(34,197,94,0.07)' }}>
                    <div style={{ fontWeight: 700, fontSize: 12.5, marginBottom: 6 }}>Datos necesarios para WhatsApp Cloud API (Meta)</div>
                    <div style={{ color: 'var(--ink-2)', fontSize: 11.5, lineHeight: 1.55 }}>
                      Usa credenciales oficiales de Meta Business: <strong>Access Token</strong>, <strong>Phone Number ID</strong> y <strong>Verify Token</strong>.
                      Al crear el despliegue, AgentForge genera el webhook que debes registrar en Meta para recibir mensajes entrantes.
                      No sirve un WhatsApp personal: necesitas WhatsApp Business Platform / Cloud API con número aprobado o conectado en Meta.
                    </div>
                  </div>
                  <label className="mono" style={{ fontSize: 11 }}>Meta Access Token *
                    <input className="input" type="password" placeholder="EAAG..." value={deployForm.whatsapp_access_token} onChange={(e) => setDeployForm((f) => ({ ...f, whatsapp_access_token: e.target.value }))} />
                    <div style={{ color: 'var(--ink-3)', fontSize: 11, marginTop: 5, fontFamily: 'var(--font)' }}>
                      Recomendado token de larga duración para producción.
                    </div>
                  </label>
                  <label className="mono" style={{ fontSize: 11 }}>Phone Number ID *
                    <input className="input" placeholder="123456789012345" value={deployForm.whatsapp_phone_number_id} onChange={(e) => setDeployForm((f) => ({ ...f, whatsapp_phone_number_id: e.target.value }))} />
                    <div style={{ color: 'var(--ink-3)', fontSize: 11, marginTop: 5, fontFamily: 'var(--font)' }}>
                      Es el ID técnico del número en Meta (no el teléfono visible).
                    </div>
                  </label>
                  <label className="mono" style={{ fontSize: 11 }}>Verify Token *
                    <input className="input" placeholder="token-seguro-verificacion" value={deployForm.whatsapp_verify_token} onChange={(e) => setDeployForm((f) => ({ ...f, whatsapp_verify_token: e.target.value }))} />
                  </label>
                  <div style={{ color: 'var(--ink-3)', fontSize: 11.5 }}>
                    Tras crear el despliegue, copia el webhook generado en la tarjeta y configúralo en Meta App Dashboard > WhatsApp > Configuration.
                  </div>
                </div>
              )}
              {deployForm.channel === 'telegram' && (
                <label className="mono" style={{ fontSize: 11 }}>Token Bot Telegram *
                  <input className="input" type="password" placeholder="1234567890:ABCDEFghij..." value={deployForm.telegram_bot_token} onChange={(e) => setDeployForm((f) => ({ ...f, telegram_bot_token: e.target.value }))} />
                </label>
              )}
              {deployForm.channel === 'web' && (
                <label className="mono" style={{ fontSize: 11 }}>Dominios permitidos
                  <input className="input" placeholder="https://cliente.com o *" value={deployForm.allowed_origins} onChange={(e) => setDeployForm((f) => ({ ...f, allowed_origins: e.target.value }))} />
                  <span style={{ display: 'block', marginTop: 5, color: 'var(--ink-3)', fontFamily: 'var(--sans)', fontSize: 11.5 }}>
                    Usa <b>*</b> solo para pruebas. En producción indica dominios separados por coma, por ejemplo: https://cliente.com, https://www.cliente.com
                  </span>
                </label>
              )}
              <label className="mono" style={{ fontSize: 11 }}>Sobrescribir prompt del sistema (opcional)
                <textarea className="input" rows={3} placeholder="Déjalo vacío para usar el prompt ya guardado del agente. Solo escribe aquí si quieres una instrucción distinta para este despliegue." value={deployForm.system_prompt_override} onChange={(e) => setDeployForm((f) => ({ ...f, system_prompt_override: e.target.value }))} />
                <div style={{ color: 'var(--ink-3)', fontSize: 11, marginTop: 5, fontFamily: 'var(--font)' }}>
                  No tienes que volver a escribir el prompt personalizado del agente. Si este campo queda vacío, se usa automáticamente el prompt guardado en el Editor.
                </div>
              </label>
            </div>
            <div className="drawer-foot">
              <button className="btn" onClick={closeNewDeploy}>Cancelar</button>
              <button className="btn btn-primary" onClick={createDeployment} disabled={deployFormLoading || !deployForm.agent_id}>
                {deployFormLoading ? 'Creando...' : (channelActionLabel[deployForm.channel] || 'Crear despliegue')}
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
};

window.Deployment = Deployment;


