<div x-data="coolifyApp" class="bg-white rounded-lg shadow">

    <!-- Overlay de carga -->
    <div x-show="loading" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex justify-center items-center z-50">
        <img src="../img/loading.gif" alt="Cargando..." class="w-16 h-16" />
    </div>

    <div class="container mx-auto p-6 w-full">

        <!-- Header -->
        <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
            <div>
                <h1 class="text-2xl font-bold">Coolify</h1>
                <p class="text-xs text-slate-500 mt-0.5">Gestión completa de aplicaciones, servicios, bases de datos, servidores y despliegues a través de la API de Coolify.</p>
            </div>
            <div class="flex items-center gap-2 flex-wrap">
                <!-- Selector de instancia -->
                <select x-show="configs.length > 1" x-model="selectedConfigId" @change="onInstanceChange()"
                    class="text-sm border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-300">
                    <template x-for="cfg in configs" :key="cfg.ID">
                        <option :value="cfg.ID" x-text="cfg.Nombre || cfg.nombre || cfg.base_url"></option>
                    </template>
                </select>
                <button @click="setTab('instancias')"
                    class="flex items-center gap-2 text-sm font-medium px-4 py-2 rounded-lg border border-indigo-200 text-indigo-600 hover:bg-indigo-50 transition">
                    + Nueva instancia
                </button>
            </div>
        </div>

        <!-- Alerta sin config -->
        <div x-show="!hasConfig && activeTab !== 'instancias'" class="mb-6 p-4 bg-yellow-50 border border-yellow-200 rounded-lg text-sm text-yellow-700">
            <strong>Sin instancia activa.</strong> Haz clic en "+ Nueva instancia" para agregar tu primera instancia de Coolify.
            El token lo generas en <strong>Coolify → Keys & Tokens → API tokens</strong>.
        </div>

        <!-- Alerta flash -->
        <div x-show="flashMsg" x-transition
             :class="flashOk ? 'bg-green-50 border-green-200 text-green-700' : 'bg-red-50 border-red-200 text-red-700'"
             class="mb-4 p-3 border rounded-lg text-sm flex items-center justify-between">
            <span x-text="flashMsg"></span>
            <button @click="flashMsg=''" class="ml-4 font-bold text-lg leading-none opacity-60 hover:opacity-100">&times;</button>
        </div>

        <!-- Tabs de navegación -->
        <div class="border-b border-gray-200 mb-6">
            <nav class="-mb-px flex flex-wrap gap-1 text-sm">
                <template x-for="tab in tabs" :key="tab.id">
                    <button @click="setTab(tab.id)"
                        :class="activeTab===tab.id ? 'border-b-2 border-indigo-500 text-indigo-600 font-semibold' : 'text-gray-500 hover:text-gray-700'"
                        class="pb-2 px-3 transition-colors flex items-center gap-1.5"
                        x-text="tab.label">
                    </button>
                </template>
            </nav>
        </div>

        <!-- Barra de herramientas global -->
        <div class="flex items-center gap-3 mb-5">
            <button @click="loadTab()" :disabled="loading"
                class="flex items-center gap-2 px-4 py-2 rounded-lg border border-gray-300 text-sm hover:bg-gray-50 transition disabled:opacity-50">
                <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" :class="loading && 'animate-spin'" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                    <path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
                </svg>
                <span x-text="loading ? 'Cargando...' : 'Actualizar'"></span>
            </button>

            <!-- Filtro apps por tag -->
            <div x-show="activeTab==='apps'" class="flex items-center gap-2">
                <input x-model="appTagFilter" @keyup.enter="loadApps()" type="text" placeholder="Filtrar por tag..."
                    class="text-sm border rounded-lg px-3 py-1.5 w-40 focus:outline-none focus:ring-2 focus:ring-indigo-300">
                <button @click="loadApps()" class="text-xs px-2 py-1.5 rounded border text-gray-500 hover:bg-gray-50">Buscar</button>
            </div>

            <span class="text-xs text-gray-400" x-show="lastFetch" x-text="'Actualizado: ' + lastFetch"></span>
        </div>

        <!-- ══════════════════════ TAB: APLICACIONES ══════════════════════ -->
        <div x-show="activeTab==='apps'">
            <div x-show="apps.length === 0 && !loading" class="text-sm text-gray-400 py-8 text-center">Sin aplicaciones. Haz clic en "Actualizar".</div>
            <div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
                <template x-for="app in apps" :key="app.uuid">
                    <div class="border rounded-xl p-4 flex flex-col gap-3 hover:shadow-md transition">
                        <!-- Nombre + status -->
                        <div class="flex items-start justify-between gap-2">
                            <div class="min-w-0">
                                <p class="font-semibold text-sm truncate" x-text="app.name"></p>
                                <p class="text-xs text-gray-400 truncate" x-text="app.fqdn || app.uuid"></p>
                            </div>
                            <span :class="statusClass(app.status)"
                                class="text-xs px-2 py-0.5 rounded-full font-medium shrink-0"
                                x-text="actionStatusText(app.uuid, app.status)">
                            </span>
                        </div>

                        <!-- Repo -->
                        <div x-show="app.git_repository" class="text-xs text-gray-500 flex items-center gap-1 truncate">
                            <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3 shrink-0" fill="currentColor" viewBox="0 0 16 16">
                                <path d="M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 1 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5zm10.5-1h-8a1 1 0 0 0-1 1v6.708A2.486 2.486 0 0 1 4.5 9h8z"/>
                            </svg>
                            <span class="truncate" x-text="app.git_repository"></span>
                        </div>
                        <div x-show="app.git_repository" class="text-xs text-gray-400 pl-4" x-text="app.git_branch ? ('Branch: ' + app.git_branch) : ''"></div>

                        <!-- Build pack -->
                        <div x-show="app.build_pack" class="text-xs text-gray-400">
                            Build: <span class="font-medium text-gray-600 capitalize" x-text="app.build_pack"></span>
                        </div>

                        <!-- Acciones -->
                        <div class="flex flex-wrap gap-2 pt-1 border-t border-gray-100">
                            <!-- Start: solo cuando está detenida / desconocida -->
                            <button x-show="!isRunning(app.status) && !isTransitioning(app.status)"
                                @click="appAction(app.uuid, 'start')"
                                :disabled="actionLoading[app.uuid]"
                                class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-green-100 text-green-700 hover:bg-green-200 transition disabled:opacity-50 font-medium">
                                <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="currentColor" viewBox="0 0 16 16"><path d="M11.596 8.697l-6.363 3.692c-.54.313-1.233-.066-1.233-.697V4.308c0-.63.692-1.01 1.233-.696l6.363 3.692a.802.802 0 0 1 0 1.393z"/></svg>
                                Start
                            </button>
                            <!-- Stop: solo cuando está corriendo -->
                            <button x-show="isRunning(app.status) || isTransitioning(app.status)"
                                @click="appAction(app.uuid, 'stop')"
                                :disabled="actionLoading[app.uuid] || isTransitioning(app.status)"
                                class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-red-100 text-red-700 hover:bg-red-200 transition disabled:opacity-50 font-medium">
                                <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="currentColor" viewBox="0 0 16 16"><path d="M5 3.5h6A1.5 1.5 0 0 1 12.5 5v6a1.5 1.5 0 0 1-1.5 1.5H5A1.5 1.5 0 0 1 3.5 11V5A1.5 1.5 0 0 1 5 3.5z"/></svg>
                                Stop
                            </button>
                            <!-- Restart: solo cuando está corriendo -->
                            <button x-show="isRunning(app.status)"
                                @click="appAction(app.uuid, 'restart')"
                                :disabled="actionLoading[app.uuid]"
                                class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-yellow-100 text-yellow-700 hover:bg-yellow-200 transition disabled:opacity-50 font-medium">
                                <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 0 0 4.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 0 1-15.357-2m15.357 2H15"/></svg>
                                Restart
                            </button>
                            <!-- Deploy: siempre disponible -->
                            <button @click="deployApp(app.uuid)"
                                :disabled="actionLoading[app.uuid]"
                                class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-indigo-100 text-indigo-700 hover:bg-indigo-200 transition disabled:opacity-50 font-medium">
                                <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 17v-6l-2 2m0 0l2-2m-2 2h14M3 9l3-3 3 3"/></svg>
                                Deploy
                            </button>
                            <button @click="openAppLogs(app)"
                                class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-gray-100 text-gray-600 hover:bg-gray-200 transition">
                                <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5.586a1 1 0 0 1 .707.293l5.414 5.414a1 1 0 0 1 .293.707V19a2 2 0 0 1-2 2z"/></svg>
                                Logs
                            </button>
                            <button @click="openAppEnvs(app)"
                                class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-gray-100 text-gray-600 hover:bg-gray-200 transition">
                                <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 0 0-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 0 0-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 0 0-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 0 0-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 0 0 1.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/></svg>
                                Envs
                            </button>
                            <button @click="openAppDeployments(app)"
                                class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-gray-100 text-gray-600 hover:bg-gray-200 transition">
                                <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 1 1-18 0 9 9 0 0 1 18 0z"/></svg>
                                Historial
                            </button>
                        </div>

                        <!-- Spinner de acción individual -->
                        <div x-show="actionLoading[app.uuid]" class="text-xs text-indigo-500 flex items-center gap-1">
                            <svg class="animate-spin h-3 w-3" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/></svg>
                            <span x-text="actionSpinnerText(app.uuid)"></span>
                        </div>
                    </div>
                </template>
            </div>
        </div>

        <!-- ══════════════════════ TAB: SERVIDORES ══════════════════════ -->
        <div x-show="activeTab==='servers'">
            <div x-show="servers.length === 0 && !loading" class="text-sm text-gray-400 py-8 text-center">Sin servidores. Haz clic en "Actualizar".</div>
            <div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
                <template x-for="srv in servers" :key="srv.uuid">
                    <div class="border rounded-xl p-4 flex flex-col gap-3 hover:shadow-md transition">
                        <div class="flex items-start justify-between gap-2">
                            <div class="min-w-0">
                                <p class="font-semibold text-sm" x-text="srv.name"></p>
                                <p class="text-xs text-gray-400 truncate" x-text="srv.description || srv.uuid"></p>
                            </div>
                            <span x-show="srv.settings && srv.settings.force_disabled"
                                class="text-xs px-2 py-0.5 rounded-full bg-red-100 text-red-700 font-medium shrink-0">Deshabilitado</span>
                            <span x-show="!srv.settings || !srv.settings.force_disabled"
                                class="text-xs px-2 py-0.5 rounded-full bg-green-100 text-green-700 font-medium shrink-0">Activo</span>
                        </div>
                        <div class="text-xs text-gray-500 space-y-1">
                            <div>IP: <span class="font-medium text-gray-700" x-text="srv.ip"></span></div>
                            <div x-show="srv.port">Puerto SSH: <span class="font-medium text-gray-700" x-text="srv.port"></span></div>
                            <div>Usuario: <span class="font-medium text-gray-700" x-text="srv.user"></span></div>
                            <div x-show="srv.proxy_type">Proxy: <span class="font-medium capitalize" x-text="srv.proxy_type"></span></div>
                        </div>
                        <div x-show="srv.settings" class="text-xs text-gray-400 flex flex-wrap gap-2">
                            <span x-show="srv.settings && srv.settings.is_build_server" class="px-2 py-0.5 bg-blue-50 text-blue-600 rounded-full">Build Server</span>
                            <span x-show="srv.settings && srv.settings.is_cloudflare_tunnel" class="px-2 py-0.5 bg-orange-50 text-orange-600 rounded-full">CF Tunnel</span>
                            <span x-show="srv.settings && srv.settings.is_jump_server" class="px-2 py-0.5 bg-purple-50 text-purple-600 rounded-full">Jump Server</span>
                            <span x-show="srv.settings && srv.settings.is_swarm_manager" class="px-2 py-0.5 bg-cyan-50 text-cyan-600 rounded-full">Swarm Manager</span>
                        </div>
                        <div class="flex flex-wrap gap-2 pt-1 border-t border-gray-100">
                            <button @click="validateServer(srv.uuid)"
                                :disabled="actionLoading[srv.uuid]"
                                class="text-xs px-2.5 py-1 rounded-lg bg-indigo-50 text-indigo-700 hover:bg-indigo-100 transition disabled:opacity-50">
                                Validar conexión
                            </button>
                            <button @click="openServerResources(srv)"
                                class="text-xs px-2.5 py-1 rounded-lg bg-gray-50 text-gray-600 hover:bg-gray-100 transition">
                                Recursos
                            </button>
                            <button @click="openServerDomains(srv)"
                                class="text-xs px-2.5 py-1 rounded-lg bg-gray-50 text-gray-600 hover:bg-gray-100 transition">
                                Dominios
                            </button>
                        </div>
                        <div x-show="actionLoading[srv.uuid]" class="text-xs text-indigo-500 flex items-center gap-1">
                            <svg class="animate-spin h-3 w-3" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/></svg>
                            Procesando...
                        </div>
                    </div>
                </template>
            </div>
        </div>

        <!-- ══════════════════════ TAB: SERVICIOS ══════════════════════ -->
        <div x-show="activeTab==='services'">
            <div x-show="services.length === 0 && !loading" class="text-sm text-gray-400 py-8 text-center">Sin servicios. Haz clic en "Actualizar".</div>
            <div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
                <template x-for="svc in services" :key="svc.uuid">
                    <div class="border rounded-xl p-4 flex flex-col gap-3 hover:shadow-md transition">
                        <div class="flex items-start justify-between gap-2">
                            <div class="min-w-0">
                                <p class="font-semibold text-sm" x-text="svc.name"></p>
                                <p class="text-xs text-gray-400 truncate" x-text="svc.fqdn || svc.uuid"></p>
                            </div>
                            <span :class="statusClass(svc.status)"
                                class="text-xs px-2 py-0.5 rounded-full font-medium shrink-0"
                                x-text="actionStatusText(svc.uuid, svc.status)">
                            </span>
                        </div>
                        <div x-show="svc.description" class="text-xs text-gray-500" x-text="svc.description"></div>
                        <div class="flex flex-wrap gap-2 pt-1 border-t border-gray-100">
                            <button x-show="!isRunning(svc.status) && !isTransitioning(svc.status)"
                                @click="serviceAction(svc.uuid, 'start')" :disabled="actionLoading[svc.uuid]"
                                class="text-xs px-2.5 py-1 rounded-lg bg-green-100 text-green-700 hover:bg-green-200 transition disabled:opacity-50 font-medium">Start</button>
                            <button x-show="isRunning(svc.status) || isTransitioning(svc.status)"
                                @click="serviceAction(svc.uuid, 'stop')" :disabled="actionLoading[svc.uuid] || isTransitioning(svc.status)"
                                class="text-xs px-2.5 py-1 rounded-lg bg-red-100 text-red-700 hover:bg-red-200 transition disabled:opacity-50 font-medium">Stop</button>
                            <button x-show="isRunning(svc.status)"
                                @click="serviceAction(svc.uuid, 'restart')" :disabled="actionLoading[svc.uuid]"
                                class="text-xs px-2.5 py-1 rounded-lg bg-yellow-100 text-yellow-700 hover:bg-yellow-200 transition disabled:opacity-50 font-medium">Restart</button>
                            <button @click="openServiceEnvs(svc)"
                                class="text-xs px-2.5 py-1 rounded-lg bg-gray-100 text-gray-600 hover:bg-gray-200 transition">Envs</button>
                        </div>
                        <div x-show="actionLoading[svc.uuid]" class="text-xs text-indigo-500 flex items-center gap-1">
                            <svg class="animate-spin h-3 w-3" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/></svg>
                            <span x-text="actionSpinnerText(svc.uuid)"></span>
                        </div>
                    </div>
                </template>
            </div>
        </div>

        <!-- ══════════════════════ TAB: BASES DE DATOS ══════════════════════ -->
        <div x-show="activeTab==='databases'">
            <div x-show="databases.length === 0 && !loading" class="text-sm text-gray-400 py-8 text-center">Sin bases de datos. Haz clic en "Actualizar".</div>
            <div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
                <template x-for="db in databases" :key="db.uuid">
                    <div class="border rounded-xl p-4 flex flex-col gap-3 hover:shadow-md transition">
                        <div class="flex items-start justify-between gap-2">
                            <div class="min-w-0">
                                <p class="font-semibold text-sm" x-text="db.name"></p>
                                <p class="text-xs text-gray-400 capitalize" x-text="db.type || db.uuid"></p>
                            </div>
                            <span :class="statusClass(db.status)"
                                class="text-xs px-2 py-0.5 rounded-full font-medium shrink-0"
                                x-text="actionStatusText(db.uuid, db.status)">
                            </span>
                        </div>
                        <div class="text-xs text-gray-500 space-y-0.5">
                            <div x-show="db.image">Imagen: <span class="font-medium text-gray-700" x-text="db.image"></span></div>
                            <div x-show="db.name">DB: <span class="font-medium text-gray-700" x-text="db.postgres_db || db.mysql_database || db.mongo_database || ''"></span></div>
                        </div>
                        <div class="flex flex-wrap gap-2 pt-1 border-t border-gray-100">
                            <button x-show="!isRunning(db.status) && !isTransitioning(db.status)"
                                @click="dbAction(db.uuid, 'start')" :disabled="actionLoading[db.uuid]"
                                class="text-xs px-2.5 py-1 rounded-lg bg-green-100 text-green-700 hover:bg-green-200 transition disabled:opacity-50 font-medium">Start</button>
                            <button x-show="isRunning(db.status) || isTransitioning(db.status)"
                                @click="dbAction(db.uuid, 'stop')" :disabled="actionLoading[db.uuid] || isTransitioning(db.status)"
                                class="text-xs px-2.5 py-1 rounded-lg bg-red-100 text-red-700 hover:bg-red-200 transition disabled:opacity-50 font-medium">Stop</button>
                            <button x-show="isRunning(db.status)"
                                @click="dbAction(db.uuid, 'restart')" :disabled="actionLoading[db.uuid]"
                                class="text-xs px-2.5 py-1 rounded-lg bg-yellow-100 text-yellow-700 hover:bg-yellow-200 transition disabled:opacity-50 font-medium">Restart</button>
                        </div>
                        <div x-show="actionLoading[db.uuid]" class="text-xs text-indigo-500 flex items-center gap-1">
                            <svg class="animate-spin h-3 w-3" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/></svg>
                            <span x-text="actionSpinnerText(db.uuid)"></span>
                        </div>
                    </div>
                </template>
            </div>
        </div>

        <!-- ══════════════════════ TAB: PROYECTOS ══════════════════════ -->
        <div x-show="activeTab==='projects'">
            <div x-show="projects.length === 0 && !loading" class="text-sm text-gray-400 py-8 text-center">Sin proyectos. Haz clic en "Actualizar".</div>
            <div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
                <template x-for="proj in projects" :key="proj.uuid">
                    <div class="border rounded-xl p-4 flex flex-col gap-3 hover:shadow-md transition">
                        <div>
                            <p class="font-semibold text-sm" x-text="proj.name"></p>
                            <p class="text-xs text-gray-400 mt-0.5" x-text="proj.description || proj.uuid"></p>
                        </div>
                        <div x-show="proj.environments && proj.environments.length > 0" class="flex flex-wrap gap-1">
                            <template x-for="env in (proj.environments || [])" :key="env.id || env.uuid || env.name || env.environment || env">
                                <span class="text-xs px-2 py-0.5 rounded-full bg-indigo-50 text-indigo-600" x-text="projectEnvLabel(env)"></span>
                            </template>
                        </div>
                        <div class="pt-1 border-t border-gray-100">
                            <button @click="openProjectEnvs(proj)"
                                class="text-xs px-2.5 py-1 rounded-lg bg-gray-50 text-gray-600 hover:bg-gray-100 transition">
                                Ver entornos
                            </button>
                        </div>
                    </div>
                </template>
            </div>
        </div>

        <!-- ══════════════════════ TAB: DESPLIEGUES ══════════════════════ -->
        <div x-show="activeTab==='deployments'">
            <div x-show="deployments.length === 0 && !loading" class="text-sm text-gray-400 py-8 text-center">Sin despliegues. Haz clic en "Actualizar".</div>
            <div class="overflow-x-auto">
                <table x-show="deployments.length > 0" class="min-w-full text-sm border-collapse">
                    <thead>
                        <tr class="bg-gray-50 text-gray-500 text-xs uppercase tracking-wide">
                            <th class="px-4 py-2 text-left font-medium">Aplicación</th>
                            <th class="px-4 py-2 text-left font-medium">Status</th>
                            <th class="px-4 py-2 text-left font-medium">Commit</th>
                            <th class="px-4 py-2 text-left font-medium">Iniciado</th>
                            <th class="px-4 py-2 text-left font-medium">Logs</th>
                        </tr>
                    </thead>
                    <tbody class="divide-y divide-gray-100">
                        <template x-for="dep in deployments" :key="dep.deployment_uuid || dep.id">
                            <tr class="hover:bg-gray-50 transition">
                                <td class="px-4 py-2.5 font-medium truncate max-w-xs" x-text="dep.application_name || dep.deployment_uuid || '—'"></td>
                                <td class="px-4 py-2.5">
                                    <span :class="deployStatusClass(dep.status)"
                                        class="text-xs px-2 py-0.5 rounded-full font-medium"
                                        x-text="dep.status || 'unknown'">
                                    </span>
                                </td>
                                <td class="px-4 py-2.5 text-gray-500 font-mono text-xs" x-text="(dep.commit || '').slice(0,7) || '—'"></td>
                                <td class="px-4 py-2.5 text-gray-400 text-xs" x-text="fmtDate(dep.created_at)"></td>
                                <td class="px-4 py-2.5">
                                    <button @click="openDeploymentLog(dep)"
                                        class="text-xs px-2 py-0.5 rounded bg-gray-100 text-gray-600 hover:bg-gray-200">Ver</button>
                                </td>
                            </tr>
                        </template>
                    </tbody>
                </table>
            </div>
        </div>

        <!-- ══════════════════════ TAB: EQUIPO ══════════════════════ -->
        <div x-show="activeTab==='team'">
            <!-- Equipo actual -->
            <div x-show="teamInfo" class="mb-6 p-4 bg-indigo-50 border border-indigo-200 rounded-xl text-sm">
                <p class="font-semibold text-indigo-800 text-base" x-text="teamInfo && teamInfo.name"></p>
                <p class="text-indigo-500 text-xs mt-0.5" x-text="teamInfo && teamInfo.description"></p>
                <div class="flex gap-4 mt-2 text-xs text-indigo-600">
                    <span>ID: <b x-text="teamInfo && teamInfo.id"></b></span>
                    <span x-show="teamInfo && teamInfo.personal_team">Equipo personal</span>
                </div>
            </div>
            <!-- Miembros -->
            <div x-show="teamMembers.length === 0 && !loading" class="text-sm text-gray-400 py-4">Sin miembros cargados.</div>
            <div x-show="teamMembers.length > 0">
                <h3 class="text-sm font-semibold mb-3 text-gray-700">Miembros del equipo</h3>
                <div class="overflow-x-auto">
                    <table class="min-w-full text-sm border-collapse">
                        <thead>
                            <tr class="bg-gray-50 text-gray-500 text-xs uppercase tracking-wide">
                                <th class="px-4 py-2 text-left font-medium">Nombre</th>
                                <th class="px-4 py-2 text-left font-medium">Email</th>
                                <th class="px-4 py-2 text-left font-medium">Rol</th>
                            </tr>
                        </thead>
                        <tbody class="divide-y divide-gray-100">
                            <template x-for="member in teamMembers" :key="member.id">
                                <tr class="hover:bg-gray-50">
                                    <td class="px-4 py-2.5 font-medium" x-text="member.name"></td>
                                    <td class="px-4 py-2.5 text-gray-500" x-text="member.email"></td>
                                    <td class="px-4 py-2.5">
                                        <span class="text-xs px-2 py-0.5 rounded-full capitalize"
                                            :class="member.role === 'admin' ? 'bg-purple-100 text-purple-700' : 'bg-gray-100 text-gray-600'"
                                            x-text="member.role || '—'">
                                        </span>
                                    </td>
                                </tr>
                            </template>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>

    </div><!-- /container -->


    <!-- ══════════════════════ MODAL: CONFIG ══════════════════════ -->
    <div x-show="configModal" x-transition class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
        <div @click.outside="configModal = false" class="bg-white rounded-2xl shadow-xl w-full max-w-md">
            <div class="p-6 border-b">
                <h2 class="text-lg font-bold">Configurar Coolify API</h2>
                <p class="text-xs text-gray-500 mt-1">Ingresa la URL base de tu instancia y el token de API.</p>
            </div>
            <div class="p-6 space-y-4">
                <div>
                    <label class="block text-sm font-medium mb-1">Nombre <span class="text-gray-400 font-normal">(referencia)</span></label>
                    <input x-model="cfgForm.nombre" type="text" placeholder="Mi Coolify"
                        class="w-full border rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-300">
                </div>
                <div>
                    <label class="block text-sm font-medium mb-1">URL Base <span class="text-red-500">*</span></label>
                    <input x-model="cfgForm.base_url" type="url" placeholder="http://tu-servidor:8000"
                        class="w-full border rounded-lg px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-indigo-300">
                    <p class="text-xs text-gray-400 mt-1">Ej: <code>http://10.0.0.1:8000</code> o <code>https://app.coolify.io</code></p>
                </div>
                <div>
                    <label class="block text-sm font-medium mb-1">API Token <span class="text-red-500">*</span></label>
                    <input x-model="cfgForm.api_token" type="password" placeholder="tok_••••••••"
                        class="w-full border rounded-lg px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-indigo-300">
                    <p class="text-xs text-gray-400 mt-1">Genera el token en <strong>Coolify → Keys & Tokens → API tokens</strong>. Deja en blanco para no cambiar el token guardado.</p>
                </div>
                <div class="flex items-center gap-2">
                    <input x-model="cfgForm.activo" type="checkbox" id="cfgActivo" class="w-4 h-4 accent-indigo-600">
                    <label for="cfgActivo" class="text-sm font-medium">Integración activa</label>
                </div>
            </div>
            <div class="p-6 border-t flex items-center gap-3 justify-between">
                <button @click="testConnection()" :disabled="cfgLoading"
                    class="flex items-center gap-1.5 text-sm px-3 py-2 rounded-lg border text-gray-600 hover:bg-gray-50 transition disabled:opacity-50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z"/>
                    </svg>
                    Probar conexión
                </button>
                <div class="flex gap-2">
                    <button @click="configModal = false"
                        class="px-4 py-2 text-sm rounded-lg border text-gray-600 hover:bg-gray-50 transition">Cancelar</button>
                    <button @click="saveConfig()" :disabled="cfgLoading"
                        class="px-4 py-2 text-sm rounded-lg text-white font-medium transition disabled:opacity-50"
                        style="background-color:#6366f1"
                        onmouseover="this.style.backgroundColor='#4f46e5'"
                        onmouseout="this.style.backgroundColor='#6366f1'">
                        <span x-text="cfgLoading ? 'Guardando...' : 'Guardar'"></span>
                    </button>
                </div>
            </div>
            <div x-show="cfgMsg" class="px-6 pb-4 text-sm" :class="cfgOk ? 'text-green-600' : 'text-red-600'" x-text="cfgMsg"></div>
        </div>
    </div>

    <!-- ══════════════════════ MODAL: LOGS ══════════════════════ -->
    <div x-show="logsModal" x-transition class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
        <div @click.outside="logsModal = false" class="bg-white rounded-2xl shadow-xl w-full max-w-3xl max-h-[80vh] flex flex-col">
            <div class="p-5 border-b flex items-center justify-between">
                <h2 class="text-base font-bold" x-text="'Logs: ' + (logsTarget ? logsTarget.name : '')"></h2>
                <div class="flex gap-2">
                    <button @click="refreshLogs()" class="text-xs px-3 py-1.5 rounded-lg border text-gray-600 hover:bg-gray-50">
                        Actualizar
                    </button>
                    <button @click="logsModal = false" class="text-gray-400 hover:text-gray-700 text-xl leading-none">&times;</button>
                </div>
            </div>
            <div class="flex-1 overflow-auto bg-gray-950 text-green-400 text-xs font-mono p-4 whitespace-pre-wrap rounded-b-2xl">
                <span x-show="logsLoading" class="text-gray-500">Cargando logs...</span>
                <span x-show="!logsLoading && !logsContent" class="text-gray-600">Sin logs disponibles.</span>
                <span x-show="!logsLoading" x-text="logsContent"></span>
            </div>
        </div>
    </div>

    <!-- ══════════════════════ MODAL: ENVS ══════════════════════ -->
    <div x-show="envsModal" x-transition class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
        <div @click.outside="envsModal = false" class="bg-white rounded-2xl shadow-xl w-full max-w-2xl max-h-[80vh] flex flex-col">
            <div class="p-5 border-b flex items-center justify-between">
                <h2 class="text-base font-bold" x-text="'Variables de entorno: ' + (envsTarget ? envsTarget.name : '')"></h2>
                <button @click="envsModal = false" class="text-gray-400 hover:text-gray-700 text-xl leading-none">&times;</button>
            </div>
            <div class="flex-1 overflow-auto p-5 space-y-2">
                <div x-show="envsLoading" class="text-sm text-gray-400 text-center py-4">Cargando variables...</div>
                <div x-show="!envsLoading && envsList.length === 0" class="text-sm text-gray-400 text-center py-4">Sin variables de entorno.</div>
                <template x-for="env in envsList" :key="env.id || env.uuid || env.key || env.name || env">
                    <div class="flex items-center gap-3 bg-gray-50 rounded-lg px-3 py-2 text-sm">
                        <span class="font-mono font-semibold text-gray-700 w-40 shrink-0 truncate" x-text="env.key || env.name || '—'"></span>
                        <span class="font-mono text-gray-500 flex-1 truncate" x-text="(env.is_shown_once || env.is_masked) ? '••••••••' : (env.value || '')"></span>
                        <span x-show="env.is_preview" class="text-xs px-1.5 py-0.5 bg-blue-100 text-blue-600 rounded">Preview</span>
                    </div>
                </template>
            </div>
            <!-- Formulario nuevo env -->
            <div class="p-5 border-t">
                <p class="text-xs font-semibold text-gray-500 mb-2 uppercase tracking-wide">Agregar variable</p>
                <div class="flex gap-2">
                    <input x-model="newEnv.key" type="text" placeholder="KEY"
                        class="font-mono text-sm border rounded-lg px-3 py-1.5 w-36 focus:outline-none focus:ring-2 focus:ring-indigo-300">
                    <input x-model="newEnv.value" type="text" placeholder="value"
                        class="font-mono text-sm border rounded-lg px-3 py-1.5 flex-1 focus:outline-none focus:ring-2 focus:ring-indigo-300">
                    <button @click="createEnv()" :disabled="envsLoading"
                        class="text-sm px-3 py-1.5 rounded-lg text-white font-medium disabled:opacity-50"
                        style="background-color:#6366f1"
                        onmouseover="this.style.backgroundColor='#4f46e5'"
                        onmouseout="this.style.backgroundColor='#6366f1'">
                        Agregar
                    </button>
                </div>
            </div>
        </div>
    </div>

    <!-- ══════════════════════ TAB: INSTANCIAS ══════════════════════ -->
    <div x-show="activeTab==='instancias'">
        <div class="flex items-center justify-between mb-4">
            <h2 class="text-base font-semibold text-gray-700">Instancias de Coolify</h2>
            <button @click="openInstModal()"
                class="flex items-center gap-2 text-sm font-medium px-4 py-2 rounded-lg text-white"
                style="background-color:#6366f1"
                onmouseover="this.style.backgroundColor='#4f46e5'"
                onmouseout="this.style.backgroundColor='#6366f1'">
                + Nueva instancia
            </button>
        </div>
        <div x-show="configs.length === 0" class="text-sm text-gray-400 py-8 text-center">
            No hay instancias configuradas. Haz clic en "+ Nueva instancia" para agregar la primera.
        </div>
        <div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
            <template x-for="cfg in configs" :key="cfg.ID || cfg.id">
                <div class="border rounded-xl p-4 flex flex-col gap-3 hover:shadow-md transition"
                    :class="selectedConfigId === (cfg.ID || cfg.id) ? 'border-indigo-200 ring-1 ring-indigo-100' : 'border-gray-200'">
                    <!-- Nombre + estado -->
                    <div class="flex items-start justify-between gap-2">
                        <div class="min-w-0">
                            <p class="font-semibold text-sm truncate" x-text="cfg.Nombre || cfg.nombre || 'Sin nombre'"></p>
                            <p class="text-xs text-gray-400 font-mono truncate" x-text="cfg.BaseURL || cfg.base_url"></p>
                        </div>
                        <span :class="(cfg.Activo || cfg.activo) ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-400'"
                            class="text-xs px-2 py-0.5 rounded-full font-medium shrink-0"
                            x-text="(cfg.Activo || cfg.activo) ? 'Activa' : 'Inactiva'">
                        </span>
                    </div>

                    <!-- Indicador de instancia en uso -->
                    <div x-show="selectedConfigId === (cfg.ID || cfg.id)" class="text-xs text-indigo-600 font-medium flex items-center gap-1">
                        <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="currentColor" viewBox="0 0 16 16">
                            <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/>
                        </svg>
                        En uso
                    </div>

                    <!-- Acciones -->
                    <div class="flex flex-wrap gap-2 pt-1 border-t border-gray-100">
                        <button x-show="selectedConfigId !== (cfg.ID || cfg.id)"
                            @click="selectedConfigId = cfg.ID || cfg.id; flash('Instancia seleccionada: ' + (cfg.Nombre || cfg.nombre))"
                            class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-indigo-100 text-indigo-700 hover:bg-indigo-200 transition font-medium">
                            <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="currentColor" viewBox="0 0 16 16"><path d="M11.596 8.697l-6.363 3.692c-.54.313-1.233-.066-1.233-.697V4.308c0-.63.692-1.01 1.233-.696l6.363 3.692a.802.802 0 0 1 0 1.393z"/></svg>
                            Usar
                        </button>
                        <button @click="testInst(cfg.ID || cfg.id)"
                            class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-gray-100 text-gray-600 hover:bg-gray-200 transition font-medium">
                            <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="currentColor" viewBox="0 0 16 16"><path d="M5.52.359A.5.5 0 0 1 6 0h4a.5.5 0 0 1 .474.658L8.694 6H12.5a.5.5 0 0 1 .395.807l-7 9a.5.5 0 0 1-.873-.454L6.823 9.5H3.5a.5.5 0 0 1-.48-.641z"/></svg>
                            Probar
                        </button>
                        <button @click="openInstModal(cfg)"
                            class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-blue-50 text-blue-600 hover:bg-blue-100 transition font-medium">
                            <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                                <path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487z"/>
                            </svg>
                            Editar
                        </button>
                        <button @click="deleteInst(cfg.ID || cfg.id)"
                            class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-red-50 text-red-600 hover:bg-red-100 transition font-medium ml-auto">
                            <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                                <path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/>
                            </svg>
                            Eliminar
                        </button>
                    </div>
                </div>
            </template>
        </div>
        <p class="text-xs text-gray-400 mt-4">
            La instancia activa se selecciona automáticamente. Puedes cambiarla con el selector del encabezado o con el botón "Usar".
            Todas las peticiones a Coolify (apps, servidores, servicios, etc.) se envían a la instancia seleccionada.
        </p>
    </div>

    <!-- ══════════════════════ MODAL: CREAR / EDITAR INSTANCIA ══════════════════════ -->
    <div x-show="instModal" x-transition class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
        <div @click.outside="instModal = false" class="bg-white rounded-2xl shadow-xl w-full max-w-md">
            <div class="p-5 border-b flex items-center justify-between">
                <h2 class="text-base font-bold" x-text="instEditId ? 'Editar instancia' : 'Nueva instancia Coolify'"></h2>
                <button @click="instModal = false" class="text-gray-400 hover:text-gray-700 text-xl leading-none">&times;</button>
            </div>
            <div class="p-5 space-y-4">
                <div>
                    <label class="block text-xs font-medium text-gray-600 mb-1">Nombre</label>
                    <input x-model="instForm.nombre" type="text" placeholder="Ej: Producción, Staging..."
                        class="w-full text-sm border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-300">
                </div>
                <div>
                    <label class="block text-xs font-medium text-gray-600 mb-1">URL base <span class="text-red-400">*</span></label>
                    <input x-model="instForm.base_url" type="url" placeholder="https://coolify.tudominio.com"
                        class="w-full text-sm border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-300">
                    <p class="text-xs text-gray-400 mt-1">Sin barra final. El sistema agrega <code>/api/v1</code> automáticamente.</p>
                </div>
                <div>
                    <label class="block text-xs font-medium text-gray-600 mb-1">API Token <span x-show="instEditId" class="text-gray-400">(dejar vacío para no cambiar)</span><span x-show="!instEditId" class="text-red-400">*</span></label>
                    <input x-model="instForm.api_token" type="password" placeholder="Token de Coolify → Keys & Tokens → API tokens"
                        class="w-full text-sm border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-300">
                </div>
                <label class="flex items-center gap-2 cursor-pointer">
                    <input x-model="instForm.activo" type="checkbox" class="rounded">
                    <span class="text-sm text-gray-700">Instancia activa</span>
                </label>
                <div x-show="instMsg" :class="instOk ? 'text-green-600 bg-green-50' : 'text-red-600 bg-red-50'"
                    class="text-xs rounded-lg px-3 py-2" x-text="instMsg"></div>
            </div>
            <div class="p-5 border-t flex items-center justify-end gap-3">
                <button @click="instModal = false" class="text-sm px-4 py-2 rounded-lg border text-gray-600 hover:bg-gray-50">Cancelar</button>
                <button @click="saveInst()" :disabled="instLoading"
                    class="text-sm px-4 py-2 rounded-lg text-white font-medium disabled:opacity-50"
                    style="background-color:#6366f1"
                    onmouseover="this.style.backgroundColor='#4f46e5'"
                    onmouseout="this.style.backgroundColor='#6366f1'"
                    x-text="instLoading ? 'Guardando...' : (instEditId ? 'Actualizar' : 'Crear instancia')">
                </button>
            </div>
        </div>
    </div>

    <!-- ══════════════════════ MODAL: DEPLOYMENTS HISTORIAL ══════════════════════ -->
    <div x-show="appDeploymentsModal" x-transition class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
        <div @click.outside="appDeploymentsModal = false" class="bg-white rounded-2xl shadow-xl w-full max-w-2xl max-h-[80vh] flex flex-col">
            <div class="p-5 border-b flex items-center justify-between">
                <h2 class="text-base font-bold" x-text="'Despliegues: ' + (appDeploymentsTarget ? appDeploymentsTarget.name : '')"></h2>
                <button @click="appDeploymentsModal = false" class="text-gray-400 hover:text-gray-700 text-xl leading-none">&times;</button>
            </div>
            <div class="flex-1 overflow-auto">
                <div x-show="appDeploymentsLoading" class="text-sm text-gray-400 text-center py-8">Cargando...</div>
                <div x-show="!appDeploymentsLoading && appDeploymentsList.length === 0" class="text-sm text-gray-400 text-center py-8">Sin despliegues registrados.</div>
                <table x-show="!appDeploymentsLoading && appDeploymentsList.length > 0" class="min-w-full text-sm">
                    <thead class="bg-gray-50 text-gray-500 text-xs uppercase">
                        <tr>
                            <th class="px-4 py-2 text-left">UUID</th>
                            <th class="px-4 py-2 text-left">Status</th>
                            <th class="px-4 py-2 text-left">Commit</th>
                            <th class="px-4 py-2 text-left">Fecha</th>
                            <th class="px-4 py-2 text-left">Logs</th>
                        </tr>
                    </thead>
                    <tbody class="divide-y divide-gray-100">
                        <template x-for="dep in appDeploymentsList" :key="dep.deployment_uuid || dep.id">
                            <tr>
                                <td class="px-4 py-2 font-mono text-xs text-gray-500" x-text="(dep.deployment_uuid || '').slice(0,8)"></td>
                                <td class="px-4 py-2">
                                    <span :class="deployStatusClass(dep.status)"
                                        class="text-xs px-2 py-0.5 rounded-full font-medium"
                                        x-text="dep.status || 'unknown'">
                                    </span>
                                </td>
                                <td class="px-4 py-2 font-mono text-xs text-gray-400" x-text="(dep.commit || '').slice(0,7) || '—'"></td>
                                <td class="px-4 py-2 text-xs text-gray-400" x-text="fmtDate(dep.created_at)"></td>
                                <td class="px-4 py-2">
                                    <button @click="openDeploymentLog(dep)"
                                        class="text-xs px-2 py-0.5 rounded bg-gray-100 text-gray-600 hover:bg-gray-200">Ver</button>
                                </td>
                            </tr>
                        </template>
                    </tbody>
                </table>
            </div>
        </div>
    </div>

    <!-- ══════════════════════ MODAL: LOG DE DEPLOYMENT ══════════════════════ -->
    <div x-show="deployLogModal" x-transition class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
        <div @click.outside="deployLogModal = false" class="bg-white rounded-2xl shadow-xl w-full max-w-3xl max-h-[80vh] flex flex-col">
            <div class="p-5 border-b flex items-center justify-between">
                <h2 class="text-base font-bold">Log de despliegue</h2>
                <button @click="deployLogModal = false" class="text-gray-400 hover:text-gray-700 text-xl leading-none">&times;</button>
            </div>
            <div class="flex-1 overflow-auto bg-gray-950 text-green-400 text-xs font-mono p-4 whitespace-pre-wrap rounded-b-2xl">
                <span x-show="!deployLogContent" class="text-gray-500">Sin log disponible.</span>
                <span x-text="deployLogContent"></span>
            </div>
        </div>
    </div>

    <!-- ══════════════════════ MODAL: SERVER RECURSOS ══════════════════════ -->
    <div x-show="serverResourcesModal" x-transition class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
        <div @click.outside="serverResourcesModal = false" class="bg-white rounded-2xl shadow-xl w-full max-w-2xl max-h-[80vh] flex flex-col">
            <div class="p-5 border-b flex items-center justify-between">
                <h2 class="text-base font-bold" x-text="'Recursos: ' + (serverTarget ? serverTarget.name : '')"></h2>
                <button @click="serverResourcesModal = false" class="text-gray-400 hover:text-gray-700 text-xl leading-none">&times;</button>
            </div>
            <div class="flex-1 overflow-auto p-5">
                <div x-show="serverResourcesLoading" class="text-sm text-gray-400 text-center py-4">Cargando recursos...</div>
                <div x-show="!serverResourcesLoading && serverResourcesList.length === 0" class="text-sm text-gray-400 text-center py-4">Sin recursos.</div>
                <div class="space-y-2">
                    <template x-for="res in serverResourcesList" :key="res.id || res.uuid">
                        <div class="flex items-center justify-between bg-gray-50 rounded-lg px-3 py-2 text-sm">
                            <span class="font-medium truncate" x-text="res.name"></span>
                            <div class="flex items-center gap-2">
                                <span class="text-xs text-gray-400 capitalize" x-text="res.type"></span>
                                <span :class="statusClass(res.status)" class="text-xs px-2 py-0.5 rounded-full font-medium" x-text="res.status || '—'"></span>
                            </div>
                        </div>
                    </template>
                </div>
            </div>
        </div>
    </div>

    <!-- ══════════════════════ MODAL: SERVER DOMINIOS ══════════════════════ -->
    <div x-show="serverDomainsModal" x-transition class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
        <div @click.outside="serverDomainsModal = false" class="bg-white rounded-2xl shadow-xl w-full max-w-lg max-h-[80vh] flex flex-col">
            <div class="p-5 border-b flex items-center justify-between">
                <h2 class="text-base font-bold" x-text="'Dominios: ' + (serverTarget ? serverTarget.name : '')"></h2>
                <button @click="serverDomainsModal = false" class="text-gray-400 hover:text-gray-700 text-xl leading-none">&times;</button>
            </div>
            <div class="flex-1 overflow-auto p-5 space-y-2">
                <div x-show="serverDomainsLoading" class="text-sm text-gray-400 text-center py-4">Cargando dominios...</div>
                <div x-show="!serverDomainsLoading && serverDomainsList.length === 0" class="text-sm text-gray-400 text-center py-4">Sin dominios.</div>
                <template x-for="(domain, idx) in serverDomainsList" :key="idx">
                    <div class="bg-gray-50 rounded-lg px-3 py-2 text-sm">
                        <p class="font-medium text-gray-700" x-text="domain.domain || domain"></p>
                        <p x-show="domain.application_name" class="text-xs text-gray-400 mt-0.5" x-text="domain.application_name"></p>
                    </div>
                </template>
            </div>
        </div>
    </div>

    <!-- ══════════════════════ MODAL: ENTORNOS PROYECTO ══════════════════════ -->
    <div x-show="projectEnvsModal" x-transition class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
        <div @click.outside="projectEnvsModal = false" class="bg-white rounded-2xl shadow-xl w-full max-w-lg max-h-[70vh] flex flex-col">
            <div class="p-5 border-b flex items-center justify-between">
                <h2 class="text-base font-bold" x-text="'Entornos: ' + (projectTarget ? projectTarget.name : '')"></h2>
                <button @click="projectEnvsModal = false" class="text-gray-400 hover:text-gray-700 text-xl leading-none">&times;</button>
            </div>
            <div class="flex-1 overflow-auto p-5 space-y-2">
                <div x-show="projectEnvsLoading" class="text-sm text-gray-400 text-center py-4">Cargando entornos...</div>
                <div x-show="!projectEnvsLoading && projectEnvsList.length === 0" class="text-sm text-gray-400 text-center py-4">Sin entornos.</div>
                <template x-for="env in projectEnvsList" :key="env.id || env.uuid || env.name || env.environment || env">
                    <div class="bg-gray-50 rounded-lg px-3 py-2 text-sm">
                        <p class="font-semibold text-gray-700" x-text="projectEnvLabel(env)"></p>
                        <p x-show="projectEnvDescription(env)" class="text-xs text-gray-400 mt-0.5" x-text="projectEnvDescription(env)"></p>
                    </div>
                </template>
            </div>
        </div>
    </div>

</div><!-- /x-data -->

<script>
document.addEventListener('alpine:init', () => {
    Alpine.data('coolifyApp', () => ({
        // Estado global
        loading: false,
        hasConfig: false,
        lastFetch: '',
        flashMsg: '',
        flashOk: true,
        activeTab: 'apps',
        tabs: [
            { id: 'apps',        label: 'Aplicaciones' },
            { id: 'servers',     label: 'Servidores' },
            { id: 'services',    label: 'Servicios' },
            { id: 'databases',   label: 'Bases de datos' },
            { id: 'projects',    label: 'Proyectos' },
            { id: 'deployments', label: 'Despliegues' },
            { id: 'team',        label: 'Equipo' },
            { id: 'instancias',  label: 'Instancias' },
        ],

        // ─── Multi-instancia ──────────────────────────
        configs: [],
        selectedConfigId: null,

        // Datos por tab
        apps: [],
        servers: [],
        services: [],
        databases: [],
        projects: [],
        deployments: [],
        teamInfo: null,
        teamMembers: [],

        // Filtros
        appTagFilter: '',

        // Action loading by uuid
        actionLoading: {},
        actionPhase: {},

        // ─── Config modal (legado) ────────────────────
        configModal: false,
        cfgLoading: false,
        cfgMsg: '',
        cfgOk: false,
        cfgForm: { nombre: '', base_url: '', api_token: '', activo: true },

        // ─── Instancia modal ──────────────────────────
        instModal: false,
        instLoading: false,
        instMsg: '',
        instOk: false,
        instEditId: null,
        instForm: { nombre: '', base_url: '', api_token: '', activo: true },

        // ─── Logs modal ───────────────────────────────
        logsModal: false,
        logsLoading: false,
        logsTarget: null,
        logsContent: '',

        // ─── Envs modal ───────────────────────────────
        envsModal: false,
        envsLoading: false,
        envsTarget: null,
        envsType: 'app', // 'app' | 'service'
        envsList: [],
        newEnv: { key: '', value: '' },

        // ─── App deployments modal ────────────────────
        appDeploymentsModal: false,
        appDeploymentsLoading: false,
        appDeploymentsTarget: null,
        appDeploymentsList: [],

        // ─── Deployment log modal ─────────────────────
        deployLogModal: false,
        deployLogContent: '',

        // ─── Server resources modal ───────────────────
        serverResourcesModal: false,
        serverResourcesLoading: false,
        serverTarget: null,
        serverResourcesList: [],

        // ─── Server domains modal ─────────────────────
        serverDomainsModal: false,
        serverDomainsLoading: false,
        serverDomainsList: [],

        // ─── Project envs modal ───────────────────────
        projectEnvsModal: false,
        projectEnvsLoading: false,
        projectTarget: null,
        projectEnvsList: [],

        // ═════════════════════════════════════════════
        async init() {
            await this.loadConfigs();
            if (this.hasConfig) this.loadTab();
        },

        // Retorna el querystring para seleccionar instancia: ?config_id=X
        configQs(sep = '?') {
            return this.selectedConfigId ? `${sep}config_id=${this.selectedConfigId}` : '';
        },

        onInstanceChange() {
            if (this.activeTab !== 'instancias') this.loadTab();
        },

        // ─── Helpers ────────────────────────────────────
        projectEnvLabel(env) {
            if (!env) return '—';
            if (typeof env === 'string') return env;
            return env.name || env.environment || env.slug || env.uuid || env.branch || 'Entorno';
        },

        projectEnvDescription(env) {
            if (!env || typeof env === 'string') return '';
            return env.description || env.branch || env.fqdn || env.url || '';
        },

        actionTextFor(action) {
            const map = {
                start: 'iniciando',
                stop: 'deteniendo',
                restart: 'reiniciando',
                deploy: 'desplegando',
            };
            return map[action] || 'procesando';
        },

        actionStatusText(uuid, status) {
            return this.actionPhase[uuid] || this.statusLabel(status);
        },

        actionSpinnerText(uuid) {
            return this.actionPhase[uuid] || 'procesando';
        },

        clearActionState(uuid) {
            const nextLoading = { ...this.actionLoading };
            delete nextLoading[uuid];
            this.actionLoading = nextLoading;

            const nextPhase = { ...this.actionPhase };
            delete nextPhase[uuid];
            this.actionPhase = nextPhase;
        },

        flash(msg, ok = true) {
            this.flashMsg = msg;
            this.flashOk = ok;
            setTimeout(() => { if (this.flashMsg === msg) this.flashMsg = ''; }, 4000);
        },

        fmtDate(dt) {
            if (!dt) return '—';
            try { return new Date(dt).toLocaleString('es', { dateStyle: 'short', timeStyle: 'short' }); }
            catch { return dt; }
        },

        // Coolify puede devolver "running:healthy", "exited:0", etc. — extrae la parte base.
        normalizeStatus(status) {
            if (!status) return '';
            return status.toLowerCase().split(':')[0].trim();
        },

        isRunning(status) {
            const s = this.normalizeStatus(status);
            return s === 'running';
        },

        isStopped(status) {
            const s = this.normalizeStatus(status);
            return s === 'stopped' || s === 'exited' || s === 'idle';
        },

        isTransitioning(status) {
            const s = this.normalizeStatus(status);
            return s === 'starting' || s === 'stopping' || s === 'restarting';
        },

        statusClass(status) {
            const s = this.normalizeStatus(status);
            if (!s) return 'bg-gray-100 text-gray-500';
            if (s === 'running')    return 'bg-green-100 text-green-700';
            if (s === 'stopped')    return 'bg-red-100 text-red-600';
            if (s === 'exited')     return 'bg-red-100 text-red-600';
            if (s === 'idle')       return 'bg-gray-100 text-gray-500';
            if (s === 'starting')   return 'bg-yellow-100 text-yellow-700';
            if (s === 'stopping')   return 'bg-yellow-100 text-yellow-700';
            if (s === 'restarting') return 'bg-yellow-100 text-yellow-700';
            if (s === 'degraded')   return 'bg-orange-100 text-orange-700';
            if (s === 'unhealthy')  return 'bg-orange-100 text-orange-700';
            return 'bg-gray-100 text-gray-500';
        },

        statusLabel(status) {
            if (!status) return 'desconocido';
            // Muestra el status original sin el sufijo de health check
            const [base, extra] = status.toLowerCase().split(':');
            const labels = { running:'corriendo', stopped:'detenido', exited:'detenido', idle:'inactivo',
                             starting:'iniciando', stopping:'deteniendo', restarting:'reiniciando',
                             degraded:'degradado', unhealthy:'no saludable' };
            const baseLabel = labels[base] || base;
            return extra ? `${baseLabel} (${extra})` : baseLabel;
        },

        deployStatusClass(status) {
            if (!status) return 'bg-gray-100 text-gray-500';
            const s = status.toLowerCase();
            if (s === 'success' || s === 'finished') return 'bg-green-100 text-green-700';
            if (s === 'error' || s === 'failed')     return 'bg-red-100 text-red-600';
            if (s === 'running' || s === 'in_progress') return 'bg-blue-100 text-blue-700';
            if (s === 'queued') return 'bg-yellow-100 text-yellow-700';
            if (s === 'cancelled') return 'bg-gray-100 text-gray-500';
            return 'bg-gray-100 text-gray-500';
        },

        // ─── Instancias ───────────────────────────────────
        async loadConfigs() {
            try {
                const r = await fetch('/app/coolify/configs');
                const j = await r.json();
                this.configs = j.items || [];
                const active = this.configs.find(c => c.Activo || c.activo);
                if (active && !this.selectedConfigId) {
                    this.selectedConfigId = active.ID || active.id;
                }
                this.hasConfig = this.configs.some(c => c.Activo || c.activo);
            } catch(e) {
                this.hasConfig = false;
            }
        },

        async openInstModal(cfg = null) {
            this.instMsg = '';
            if (!cfg) {
                this.instEditId = null;
                this.instForm = { nombre: '', base_url: '', api_token: '', activo: true };
                this.instModal = true;
                return;
            }
            const id = cfg.ID || cfg.id;
            this.instEditId = id;
            // Valores locales como fallback mientras carga
            this.instForm = { nombre: cfg.Nombre || cfg.nombre || '', base_url: cfg.BaseURL || cfg.base_url || '', api_token: '', activo: cfg.Activo ?? cfg.activo ?? true };
            this.instModal = true;
            // Refresco desde el server para garantizar datos actualizados
            try {
                const r = await fetch(`/app/coolify/configs/${id}`);
                if (r.ok) {
                    const j = await r.json();
                    this.instForm = { nombre: j.nombre || '', base_url: j.base_url || '', api_token: '', activo: j.activo ?? true };
                }
            } catch(_) {}
        },

        async saveInst() {
            this.instLoading = true;
            this.instMsg = '';
            try {
                const isEdit = !!this.instEditId;
                const url  = isEdit ? `/app/coolify/configs/${this.instEditId}` : '/app/coolify/configs';
                const meth = isEdit ? 'PUT' : 'POST';
                const r = await fetch(url, {
                    method: meth,
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify(this.instForm)
                });
                const j = await r.json();
                if (j.ok) {
                    this.instOk = true;
                    this.instMsg = isEdit ? 'Instancia actualizada.' : 'Instancia creada.';
                    await this.loadConfigs();
                    if (!isEdit && j.id) this.selectedConfigId = j.id;
                    setTimeout(() => { this.instModal = false; if (this.hasConfig && this.activeTab !== 'instancias') this.loadTab(); }, 1200);
                } else {
                    this.instOk = false;
                    this.instMsg = j.error || 'Error al guardar.';
                }
            } catch(e) {
                this.instOk = false;
                this.instMsg = 'Error de red: ' + e.message;
            } finally {
                this.instLoading = false;
            }
        },

        async deleteInst(id) {
            if (!confirm('¿Eliminar esta instancia de Coolify?')) return;
            const r = await fetch(`/app/coolify/configs/${id}`, { method: 'DELETE' });
            const j = await r.json();
            if (j.ok) {
                if (this.selectedConfigId === id) this.selectedConfigId = null;
                await this.loadConfigs();
                this.flash('Instancia eliminada.');
            } else {
                this.flash(j.error || 'Error al eliminar.', false);
            }
        },

        async testInst(id) {
            const r = await fetch(`/app/coolify/configs/${id}/test`);
            if (r.ok) {
                this.flash('✓ Conexión exitosa con la instancia.');
            } else {
                const j = await r.json().catch(() => ({}));
                this.flash('Error: ' + (j.error || r.status), false);
            }
        },

        // Legado: mantiene compatibilidad con código existente
        openConfigModal() { this.openInstModal(); },
        async testConnection() { if (this.selectedConfigId) await this.testInst(this.selectedConfigId); },

        // ─── Tab navigation ──────────────────────────────
        setTab(tab) {
            this.activeTab = tab;
            this.loadTab();
        },

        loadTab() {
            if (this.activeTab === 'instancias') return; // se carga en init
            if (!this.hasConfig) return;
            const map = {
                apps:        () => this.loadApps(),
                servers:     () => this.loadServers(),
                services:    () => this.loadServices(),
                databases:   () => this.loadDatabases(),
                projects:    () => this.loadProjects(),
                deployments: () => this.loadDeployments(),
                team:        () => this.loadTeam(),
            };
            const fn = map[this.activeTab];
            if (fn) fn();
        },

        // ─── Loaders ─────────────────────────────────────
        async loadApps() {
            this.loading = true;
            try {
                let qs = this.configQs();
                if (this.appTagFilter) qs += (qs ? '&' : '?') + `tag=${encodeURIComponent(this.appTagFilter)}`;
                const r = await fetch('/app/coolify/apps' + qs);
                const j = await r.json();
                this.apps = Array.isArray(j) ? j : (j.data || []);
                const al = {}; this.apps.forEach(a => { al[a.uuid] = false; }); this.actionLoading = al;
                this.lastFetch = this.fmtDate(new Date().toISOString());
            } catch(e) { this.flash('Error cargando apps: ' + e.message, false); }
            finally { this.loading = false; }
        },

        async loadServers() {
            this.loading = true;
            try {
                const r = await fetch('/app/coolify/servers' + this.configQs());
                const j = await r.json();
                this.servers = Array.isArray(j) ? j : (j.data || []);
                this.lastFetch = this.fmtDate(new Date().toISOString());
            } catch(e) { this.flash('Error cargando servidores: ' + e.message, false); }
            finally { this.loading = false; }
        },

        async loadServices() {
            this.loading = true;
            try {
                const r = await fetch('/app/coolify/services' + this.configQs());
                const j = await r.json();
                this.services = Array.isArray(j) ? j : (j.data || []);
                const sl = { ...this.actionLoading }; this.services.forEach(s => { if (!(s.uuid in sl)) sl[s.uuid] = false; }); this.actionLoading = sl;
                this.lastFetch = this.fmtDate(new Date().toISOString());
            } catch(e) { this.flash('Error cargando servicios: ' + e.message, false); }
            finally { this.loading = false; }
        },

        async loadDatabases() {
            this.loading = true;
            try {
                const r = await fetch('/app/coolify/databases' + this.configQs());
                const j = await r.json();
                this.databases = Array.isArray(j) ? j : (j.data || []);
                const dl = { ...this.actionLoading }; this.databases.forEach(d => { if (!(d.uuid in dl)) dl[d.uuid] = false; }); this.actionLoading = dl;
                this.lastFetch = this.fmtDate(new Date().toISOString());
            } catch(e) { this.flash('Error cargando bases de datos: ' + e.message, false); }
            finally { this.loading = false; }
        },

        async loadProjects() {
            this.loading = true;
            try {
                const r = await fetch('/app/coolify/projects' + this.configQs());
                const j = await r.json();
                this.projects = Array.isArray(j) ? j : (j.data || []);
                this.lastFetch = this.fmtDate(new Date().toISOString());
            } catch(e) { this.flash('Error cargando proyectos: ' + e.message, false); }
            finally { this.loading = false; }
        },

        async loadDeployments() {
            this.loading = true;
            try {
                const r = await fetch('/app/coolify/deployments' + this.configQs());
                const j = await r.json();
                this.deployments = Array.isArray(j) ? j
                    : (j.deployments || j.items || j.data || j.result || j.deployments_list || []);
                this.lastFetch = this.fmtDate(new Date().toISOString());
            } catch(e) { this.flash('Error cargando despliegues: ' + e.message, false); }
            finally { this.loading = false; }
        },

        async loadTeam() {
            this.loading = true;
            try {
                const qs = this.configQs();
                const [rt, rm] = await Promise.all([
                    fetch('/app/coolify/team' + qs),
                    fetch('/app/coolify/team/members' + qs),
                ]);
                const jt = await rt.json();
                const jm = await rm.json();
                this.teamInfo    = Array.isArray(jt) ? (jt[0] || null) : jt;
                this.teamMembers = Array.isArray(jm) ? jm : (jm.data || []);
                this.lastFetch   = this.fmtDate(new Date().toISOString());
            } catch(e) { this.flash('Error cargando equipo: ' + e.message, false); }
            finally { this.loading = false; }
        },

        // ─── App actions ─────────────────────────────────
        async appAction(uuid, action) {
            const prev = { ...this.actionLoading };
            prev[uuid] = true;
            this.actionLoading = prev;
            const phase = this.actionTextFor(action);
            this.actionPhase = { ...this.actionPhase, [uuid]: phase };
            let clearNow = true;
            try {
                const r = await fetch(`/app/coolify/apps/${uuid}/${action}${this.configQs()}`);
                const j = await r.json();
                if (r.ok) {
                    clearNow = false;
                    this.flash(`App ${phase} correctamente.`);
                    setTimeout(async () => {
                        await this.loadApps();
                        this.clearActionState(uuid);
                    }, 2000);
                } else {
                    this.flash((j.message || j.error || `Error en ${action}`), false);
                }
            } catch(e) { this.flash('Error: ' + e.message, false); }
            finally {
                if (clearNow) this.clearActionState(uuid);
            }
        },

        async deployApp(uuid) {
            const prev = { ...this.actionLoading };
            prev[uuid] = true;
            this.actionLoading = prev;
            this.actionPhase = { ...this.actionPhase, [uuid]: this.actionTextFor('deploy') };
            let clearNow = true;
            try {
                const r = await fetch(`/app/coolify/apps/${uuid}/deploy${this.configQs()}`, { method: 'POST' });
                const j = await r.json();
                if (r.ok) {
                    clearNow = false;
                    this.flash('Deploy disparado correctamente.');
                    setTimeout(() => this.clearActionState(uuid), 3000);
                } else {
                    this.flash((j.message || j.error || 'Error al hacer deploy'), false);
                }
            } catch(e) { this.flash('Error: ' + e.message, false); }
            finally {
                if (clearNow) this.clearActionState(uuid);
            }
        },

        // ─── Logs ────────────────────────────────────────
        async openAppLogs(app) {
            this.logsTarget  = app;
            this.logsContent = '';
            this.logsModal   = true;
            this.logsLoading = true;
            try {
                const r = await fetch(`/app/coolify/apps/${app.uuid}/logs`);
                const j = await r.json();
                this.logsContent = typeof j === 'string' ? j
                    : (j.logs || j.data || JSON.stringify(j, null, 2));
            } catch(e) { this.logsContent = 'Error: ' + e.message; }
            finally { this.logsLoading = false; }
        },

        async refreshLogs() {
            if (!this.logsTarget) return;
            this.logsLoading = true;
            this.logsContent = '';
            try {
                const r = await fetch(`/app/coolify/apps/${this.logsTarget.uuid}/logs`);
                const j = await r.json();
                this.logsContent = typeof j === 'string' ? j
                    : (j.logs || j.data || JSON.stringify(j, null, 2));
            } catch(e) { this.logsContent = 'Error: ' + e.message; }
            finally { this.logsLoading = false; }
        },

        // ─── Envs ────────────────────────────────────────
        async openAppEnvs(app) {
            this.envsTarget  = app;
            this.envsType    = 'app';
            this.envsList    = [];
            this.newEnv      = { key: '', value: '' };
            this.envsModal   = true;
            this.envsLoading = true;
            try {
                const r = await fetch(`/app/coolify/apps/${app.uuid}/envs`);
                const j = await r.json();
                this.envsList = Array.isArray(j) ? j : (j.data || j.variables || j.envs || j.items || j.environment_variables || []);
            } catch(e) { this.flash('Error: ' + e.message, false); }
            finally { this.envsLoading = false; }
        },

        async openServiceEnvs(svc) {
            this.envsTarget  = svc;
            this.envsType    = 'service';
            this.envsList    = [];
            this.newEnv      = { key: '', value: '' };
            this.envsModal   = true;
            this.envsLoading = true;
            try {
                const r = await fetch(`/app/coolify/services/${svc.uuid}/envs`);
                const j = await r.json();
                this.envsList = Array.isArray(j) ? j : (j.data || j.variables || j.envs || j.items || j.environment_variables || []);
            } catch(e) { this.flash('Error: ' + e.message, false); }
            finally { this.envsLoading = false; }
        },

        async createEnv() {
            if (!this.newEnv.key) return;
            const uuid = this.envsTarget.uuid;
            const endpoint = this.envsType === 'app'
                ? `/app/coolify/apps/${uuid}/envs`
                : `/app/coolify/services/${uuid}/envs`;
            this.envsLoading = true;
            try {
                const r = await fetch(endpoint, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ key: this.newEnv.key, value: this.newEnv.value })
                });
                const j = await r.json();
                if (r.ok) {
                    this.flash('Variable creada.');
                    this.newEnv = { key: '', value: '' };
                    // Recargar lista
                    const r2 = await fetch(endpoint.replace(/\/envs$/, '/envs'));
                    const j2 = await r2.json();
                    this.envsList = Array.isArray(j2) ? j2 : (j2.data || j2.variables || j2.envs || j2.items || j2.environment_variables || []);
                } else {
                    this.flash(j.message || j.error || 'Error al crear variable.', false);
                }
            } catch(e) { this.flash('Error: ' + e.message, false); }
            finally { this.envsLoading = false; }
        },

        // ─── App deployments historial ───────────────────
        async openAppDeployments(app) {
            this.appDeploymentsTarget = app;
            this.appDeploymentsList   = [];
            this.appDeploymentsModal  = true;
            this.appDeploymentsLoading = true;
            try {
                const r = await fetch(`/app/coolify/apps/${app.uuid}/deployments`);
                const j = await r.json();
                let list = Array.isArray(j) ? j
                    : (j.deployments || j.items || j.data || j.result || j.deployments_list || []);
                // Si la respuesta es vacía pero tiene data a nivel superior, prueba parsear como objeto wrapper
                if (!Array.isArray(list) && typeof list === 'object' && list !== null) {
                    list = Object.values(list).filter(v => Array.isArray(v))[0] || [];
                }
                this.appDeploymentsList = Array.isArray(list) ? list : [];
            } catch(e) { this.flash('Error: ' + e.message, false); }
            finally { this.appDeploymentsLoading = false; }
        },

        // ─── Deployment log ──────────────────────────────
        openDeploymentLog(dep) {
            this.deployLogContent = dep.logs || dep.log || JSON.stringify(dep, null, 2);
            this.deployLogModal   = true;
        },

        // ─── Service actions ─────────────────────────────
        async serviceAction(uuid, action) {
            const prev = { ...this.actionLoading };
            prev[uuid] = true;
            this.actionLoading = prev;
            const phase = this.actionTextFor(action);
            this.actionPhase = { ...this.actionPhase, [uuid]: phase };
            let clearNow = true;
            try {
                const r = await fetch(`/app/coolify/services/${uuid}/${action}`);
                const j = await r.json();
                if (r.ok) {
                    clearNow = false;
                    this.flash(`Servicio ${phase} correctamente.`);
                    setTimeout(async () => {
                        await this.loadServices();
                        this.clearActionState(uuid);
                    }, 2000);
                } else {
                    this.flash(j.message || j.error || `Error en ${action}`, false);
                }
            } catch(e) { this.flash('Error: ' + e.message, false); }
            finally {
                if (clearNow) this.clearActionState(uuid);
            }
        },

        // ─── DB actions ──────────────────────────────────
        async dbAction(uuid, action) {
            const prev = { ...this.actionLoading };
            prev[uuid] = true;
            this.actionLoading = prev;
            const phase = this.actionTextFor(action);
            this.actionPhase = { ...this.actionPhase, [uuid]: phase };
            let clearNow = true;
            try {
                const r = await fetch(`/app/coolify/databases/${uuid}/${action}`);
                const j = await r.json();
                if (r.ok) {
                    clearNow = false;
                    this.flash(`Base de datos ${phase} correctamente.`);
                    setTimeout(async () => {
                        await this.loadDatabases();
                        this.clearActionState(uuid);
                    }, 2000);
                } else {
                    this.flash(j.message || j.error || `Error en ${action}`, false);
                }
            } catch(e) { this.flash('Error: ' + e.message, false); }
            finally {
                if (clearNow) this.clearActionState(uuid);
            }
        },

        // ─── Server actions ──────────────────────────────
        async validateServer(uuid) {
            const prev = { ...this.actionLoading };
            prev[uuid] = true;
            this.actionLoading = prev;
            try {
                const r = await fetch(`/app/coolify/servers/${uuid}/validate`);
                const j = await r.json();
                if (r.ok) {
                    this.flash('Servidor validado correctamente.');
                } else {
                    this.flash(j.message || j.error || 'Error al validar', false);
                }
            } catch(e) { this.flash('Error: ' + e.message, false); }
            finally {
                const next = { ...this.actionLoading };
                next[uuid] = false;
                this.actionLoading = next;
            }
        },

        async openServerResources(srv) {
            this.serverTarget         = srv;
            this.serverResourcesList  = [];
            this.serverResourcesModal = true;
            this.serverResourcesLoading = true;
            try {
                const r = await fetch(`/app/coolify/servers/${srv.uuid}/resources`);
                const j = await r.json();
                this.serverResourcesList = Array.isArray(j) ? j : (j.data || []);
            } catch(e) { this.flash('Error: ' + e.message, false); }
            finally { this.serverResourcesLoading = false; }
        },

        async openServerDomains(srv) {
            this.serverTarget       = srv;
            this.serverDomainsList  = [];
            this.serverDomainsModal = true;
            this.serverDomainsLoading = true;
            try {
                const r = await fetch(`/app/coolify/servers/${srv.uuid}/domains`);
                const j = await r.json();
                this.serverDomainsList = Array.isArray(j) ? j : (j.data || []);
            } catch(e) { this.flash('Error: ' + e.message, false); }
            finally { this.serverDomainsLoading = false; }
        },

        // ─── Project envs ────────────────────────────────
        async openProjectEnvs(proj) {
            this.projectTarget    = proj;
            this.projectEnvsList  = Array.isArray(proj.environments) ? proj.environments : [];
            this.projectEnvsModal = true;
            this.projectEnvsLoading = true;
            try {
                const r = await fetch(`/app/coolify/projects/${proj.uuid}/environments`);
                const j = await r.json();
                this.projectEnvsList = Array.isArray(j) ? j
                    : (j.environments || j.data || j.items || j.projects_environments || []);
            } catch(e) { this.flash('Error: ' + e.message, false); }
            finally { this.projectEnvsLoading = false; }
        },

    }));
});
</script>
