{"id":308,"date":"2026-02-05T17:54:32","date_gmt":"2026-02-05T17:54:32","guid":{"rendered":"https:\/\/saca-plan.com\/inicio\/?page_id=308"},"modified":"2026-06-09T15:36:41","modified_gmt":"2026-06-09T15:36:41","slug":"portal-eventos","status":"publish","type":"page","link":"https:\/\/saca-plan.com\/inicio\/portal-eventos\/","title":{"rendered":"Portal Eventos"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"es\">\n\n<head>\n    <meta charset=\"UTF-8\" \/>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \/>\n    <title>Mis Eventos &#8211; Saca Plan<\/title>\n    <script src=\"https:\/\/cdn.tailwindcss.com\"><\/script>\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/@supabase\/supabase-js@2\"><\/script>\n    <style>\n        .form-label {\n            @apply block text-sm font-semibold text-gray-800 mb-2 mt-4;\n        }\n\n        .form-input {\n            @apply w-full p-3 border-2 border-gray-400 rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 shadow-sm;\n        }\n\n        .form-input:read-only {\n            @apply bg-gray-100 border-gray-300 cursor-not-allowed;\n        }\n\n        input[type=\"file\"].form-input {\n            @apply border-2 border-dashed border-blue-400 bg-blue-50 p-4 cursor-pointer;\n        }\n\n        input[type=\"file\"].form-input:hover {\n            @apply border-blue-600 bg-blue-100;\n        }\n\n        textarea.form-input {\n            resize: vertical;\n            min-height: 100px;\n            width: 100%;\n        }\n\n        .btn-primary {\n            @apply bg-blue-600 text-white font-semibold py-3 px-6 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed shadow-md;\n        }\n\n        .btn-secondary {\n            @apply bg-gray-300 text-gray-800 font-semibold py-3 px-6 rounded-lg hover:bg-gray-400 transition-colors shadow-md;\n        }\n\n        .btn-danger {\n            @apply bg-red-600 text-white font-semibold py-2 px-4 rounded-lg hover:bg-red-700 transition-colors shadow-md;\n        }\n\n        .badge {\n            @apply inline-block px-3 py-1 text-xs font-semibold rounded-full;\n        }\n\n        .badge-borrador {\n            @apply bg-gray-200 text-gray-700;\n        }\n\n        .badge-enviado {\n            @apply bg-blue-200 text-blue-700;\n        }\n\n        .badge-en_revision {\n            @apply bg-yellow-200 text-yellow-800;\n        }\n\n        .badge-aprobado {\n            @apply bg-green-200 text-green-800;\n        }\n\n        .badge-rechazado {\n            @apply bg-red-200 text-red-800;\n        }\n\n        .badge-publicado {\n            @apply bg-purple-200 text-purple-800;\n        }\n\n        .hidden {\n            display: none !important;\n        }\n\n        \/* Mejor visualizaci\u00f3n de placeholders *\/\n        input::placeholder,\n        textarea::placeholder {\n            color: #9CA3AF;\n            opacity: 0.7;\n        }\n\n        \/* Mejorar inputs de fecha y hora *\/\n        input[type=\"date\"],\n        input[type=\"time\"] {\n            color-scheme: light;\n        }\n\n        \/* Secciones del formulario *\/\n        .form-section {\n            transition: all 0.2s ease;\n        }\n\n        .form-section:hover {\n            transform: translateY(-2px);\n            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n        }\n\n        \/* Sobrescribir estilos de WordPress *\/\n        .portal-eventos-wrapper,\n        .portal-eventos-wrapper *,\n        .portal-eventos-wrapper>*,\n        .portal-eventos-wrapper .container,\n        .portal-eventos-wrapper #app-screen,\n        .portal-eventos-wrapper #app-screen>*,\n        .portal-eventos-wrapper #auth-screen,\n        .portal-eventos-wrapper #auth-screen>* {\n            max-width: none !important;\n            margin-left: 0 !important;\n            margin-right: 0 !important;\n        }\n\n        .portal-eventos-wrapper #app-screen .container.max-w-7xl,\n        .portal-eventos-wrapper nav>.container.max-w-7xl {\n            max-width: 1280px !important;\n            margin-left: auto !important;\n            margin-right: auto !important;\n        }\n\n        \/* Forzar que los inputs ocupen todo el ancho disponible *\/\n        .portal-eventos-wrapper .form-input,\n        .portal-eventos-wrapper input.form-input,\n        .portal-eventos-wrapper textarea.form-input,\n        .portal-eventos-wrapper select.form-input {\n            width: 100% !important;\n            max-width: 100% !important;\n        }\n\n        \/* Eliminar restricciones de WordPress para esta p\u00e1gina *\/\n        .portal-eventos-wrapper.is-layout-constrained> :where(:not(.alignleft):not(.alignright):not(.alignfull)),\n        .portal-eventos-wrapper .is-layout-constrained> :where(:not(.alignleft):not(.alignright):not(.alignfull)) {\n            max-width: none !important;\n            margin-left: 0 !important;\n            margin-right: 0 !important;\n        }\n    <\/style>\n    <script\n        src=\"https:\/\/maps.googleapis.com\/maps\/api\/js?key=AIzaSyAUHrO1Xc9to3V8La2Yv4OchGBla2ybocc&#038;libraries=places&#038;callback=initGooglePlaces&#038;loading=async\"\n        async defer><\/script>\n    <script>\n        window.__gmapsInitRequested = false;\n        window.initGooglePlaces = function () {\n            window.__gmapsInitRequested = true;\n        };\n    <\/script>\n<\/head>\n\n<body class=\"bg-gray-50 font-sans\">\n    <div class=\"portal-eventos-wrapper\">\n        <!-- Auth Screen -->\n        <div id=\"auth-screen\" class=\"min-h-screen flex items-center justify-center p-4\">\n            <div class=\"bg-white rounded-xl shadow-lg p-8 w-full max-w-md\">\n                <h1 class=\"text-2xl font-bold text-gray-800 mb-6 text-center\">Saca Plan &#8211; Mis Eventos<\/h1>\n\n                <div id=\"login-form\">\n                    <h2 class=\"text-lg font-semibold mb-4\">Iniciar Sesi\u00f3n<\/h2>\n                    <div class=\"space-y-4\">\n                        <div>\n                            <label class=\"form-label\">Email<\/label>\n                            <input type=\"email\" id=\"login-email\" class=\"form-input\" placeholder=\"tu@email.com\"\n                                required \/>\n                        <\/div>\n                        <div>\n                            <label class=\"form-label\">Contrase\u00f1a<\/label>\n                            <input type=\"password\" id=\"login-password\" class=\"form-input\" placeholder=\"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\"\n                                required \/>\n                        <\/div>\n                        <button onclick=\"handleLogin()\" class=\"btn-primary w-full\">Entrar<\/button>\n                        <p class=\"text-center text-sm text-gray-600\">\n                            \u00bfNo tienes cuenta?\n                            <button onclick=\"toggleAuthMode()\" class=\"text-blue-600 hover:underline\">Reg\u00edstrate\n                                aqu\u00ed<\/button>\n                        <\/p>\n                    <\/div>\n                <\/div>\n\n                <div id=\"signup-form\" class=\"hidden\">\n                    <h2 class=\"text-lg font-semibold mb-4\">Crear Cuenta<\/h2>\n                    <div class=\"space-y-4\">\n                        <div>\n                            <label class=\"form-label\">Email<\/label>\n                            <input type=\"email\" id=\"signup-email\" class=\"form-input\" placeholder=\"tu@email.com\"\n                                required \/>\n                        <\/div>\n                        <div>\n                            <label class=\"form-label\">Contrase\u00f1a<\/label>\n                            <input type=\"password\" id=\"signup-password\" class=\"form-input\" placeholder=\"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\"\n                                required \/>\n                        <\/div>\n                        <button onclick=\"handleSignup()\" class=\"btn-primary w-full\">Crear Cuenta<\/button>\n                        <p class=\"text-center text-sm text-gray-600\">\n                            \u00bfYa tienes cuenta?\n                            <button onclick=\"toggleAuthMode()\" class=\"text-blue-600 hover:underline\">Inicia\n                                sesi\u00f3n<\/button>\n                        <\/p>\n                    <\/div>\n                <\/div>\n\n                <div id=\"auth-message\" class=\"mt-4 p-3 rounded-lg text-sm hidden\"><\/div>\n            <\/div>\n        <\/div>\n\n        <!-- Main App -->\n        <div id=\"app-screen\" class=\"hidden min-h-screen\">\n            <nav class=\"bg-white shadow-md\">\n                <div class=\"container mx-auto px-8 py-4 flex justify-between items-center max-w-7xl\">\n                    <h1 class=\"text-xl font-bold text-gray-800\">Mis Eventos<\/h1>\n                    <div class=\"flex items-center gap-4\">\n                        <span id=\"user-email\" class=\"text-sm text-gray-600\"><\/span>\n                        <button onclick=\"handleLogout()\" class=\"text-sm text-red-600 hover:underline\">Cerrar\n                            Sesi\u00f3n<\/button>\n                    <\/div>\n                <\/div>\n            <\/nav>\n\n            <div class=\"container mx-auto p-6 max-w-7xl px-8\">\n                <!-- Tabs -->\n                <div class=\"flex gap-2 mb-6 border-b border-gray-200\">\n                    <button onclick=\"mostrarTab('lista')\"\n                        class=\"tab-btn px-6 py-3 font-medium rounded-t-lg transition-colors\" data-tab=\"lista\">Mis\n                        Eventos<\/button>\n                    <button onclick=\"mostrarTab('nuevo')\"\n                        class=\"tab-btn px-6 py-3 font-medium rounded-t-lg transition-colors\" data-tab=\"nuevo\">Nuevo\n                        Evento<\/button>\n                <\/div>\n\n                <!-- Tab: Lista de eventos -->\n                <div id=\"tab-lista\" class=\"hidden\">\n                    <div class=\"mb-6 bg-white p-4 rounded-lg shadow-md\">\n                        <label class=\"form-label mb-2\">\ud83d\udd0d Buscar eventos<\/label>\n                        <input type=\"text\" id=\"search-input\" placeholder=\"Escribe el nombre del evento...\"\n                            oninput=\"filtrarMisEventos()\" class=\"form-input\" \/>\n                    <\/div>\n                    <div id=\"lista-eventos\" class=\"space-y-4\"><\/div>\n                <\/div>\n\n                <!-- Tab: Nuevo\/Editar evento -->\n                <div id=\"tab-nuevo\" class=\"hidden\">\n                    <div class=\"bg-white rounded-xl shadow-lg p-8\">\n                        <h2 id=\"form-titulo\"\n                            class=\"text-2xl font-bold mb-6 text-gray-800 pb-4 border-b-2 border-gray-200\">\n                            Nuevo Evento<\/h2>\n\n                        <form id=\"evento-form\" class=\"space-y-6\">\n                            <input type=\"hidden\" id=\"evento-id\" \/>\n\n                            <!-- Secci\u00f3n: Informaci\u00f3n B\u00e1sica -->\n                            <div class=\"form-section bg-gray-50 p-5 rounded-lg border-2 border-gray-300 shadow-sm\">\n                                <h3 class=\"text-lg font-bold text-gray-800 mb-4 pb-2 border-b-2 border-gray-300\">\ud83d\udcdd\n                                    Informaci\u00f3n B\u00e1sica<\/h3>\n\n                                <div class=\"grid grid-cols-1 lg:grid-cols-3 gap-4\">\n                                    <div class=\"lg:col-span-2\">\n                                        <label class=\"form-label\">Nombre del evento *<\/label>\n                                        <input name=\"nombre\" id=\"nombre\" class=\"form-input\" required maxlength=\"255\"\n                                            placeholder=\"Ej: Concierto de Rock en Le\u00f3n\" \/>\n                                    <\/div>\n\n                                    <div>\n                                        <label class=\"form-label\">Intereses * <span\n                                                style=\"font-size:12px; font-weight:normal; color:#666\">(selecciona uno o\n                                                m\u00e1s)<\/span><\/label>\n                                        <div id=\"interes\" class=\"form-input\"\n                                            style=\"max-height:140px; overflow-y:auto; display:flex; flex-direction:column; gap:4px; padding:8px;\">\n                                        <\/div>\n                                    <\/div>\n                                <\/div>\n\n                                <div>\n                                    <label class=\"form-label\">Descripci\u00f3n *<\/label>\n                                    <textarea name=\"descripcion\" id=\"descripcion\" class=\"form-input\" rows=\"4\"\n                                        placeholder=\"Describe el evento, que incluye, p\u00fablico objetivo, etc.\"\n                                        required><\/textarea>\n                                <\/div>\n\n                                <div>\n                                    <label class=\"form-label\">Keywords (separadas por comas)<\/label>\n                                    <input name=\"keywords\" id=\"keywords\" class=\"form-input\"\n                                        placeholder=\"Ej: rock, musica, concierto\" \/>\n                                    <p class=\"text-xs text-gray-600 mt-1\">Se usar\u00e1n para b\u00fasqueda y recomendaciones<\/p>\n                                <\/div>\n                            <\/div>\n\n                            <!-- Secci\u00f3n: Imagen -->\n                            <div class=\"form-section bg-blue-50 p-5 rounded-lg border-2 border-blue-300 shadow-sm\">\n                                <h3 class=\"text-lg font-bold text-blue-800 mb-4 pb-2 border-b-2 border-blue-300\">\ud83d\uddbc\ufe0f\n                                    Imagen\n                                    del Evento<\/h3>\n\n                                <div>\n                                    <label class=\"form-label\">Imagen del evento<\/label>\n                                    <input type=\"file\" id=\"imagen-file\" accept=\"image\/jpeg,image\/png,image\/webp\"\n                                        class=\"form-input\" \/>\n                                    <p class=\"text-xs text-gray-600 mt-1\">\ud83d\udccc JPG, PNG o WEBP. Se comprimir\u00e1\n                                        autom\u00e1ticamente.\n                                        M\u00e1ximo 8MB.<\/p>\n                                    <div id=\"imagen-preview\" class=\"mt-4 hidden\">\n                                        <p class=\"text-sm text-gray-600 mb-2\">Vista previa:<\/p>\n                                        <img id=\"imagen-preview-img\"\n                                            class=\"h-40 w-auto object-cover rounded-lg border-2 border-gray-300\" \/>\n                                    <\/div>\n                                <\/div>\n                            <\/div>\n\n                            <!-- Secci\u00f3n: Ubicaci\u00f3n -->\n                            <div class=\"form-section bg-green-50 p-5 rounded-lg border-2 border-green-300 shadow-sm\">\n                                <h3 class=\"text-lg font-bold text-green-800 mb-4 pb-2 border-b-2 border-green-300\">\ud83d\udccd\n                                    Ubicaci\u00f3n<\/h3>\n\n                                <div>\n                                    <label class=\"form-label\">Ubicaci\u00f3n (Google Maps) *<\/label>\n                                    <input name=\"ubicacion\" id=\"ubicacion\" placeholder=\"Busca en Google Maps...\"\n                                        class=\"form-input !border-blue-600\" required \/>\n                                    <p class=\"text-xs text-gray-600 mt-1\">\ud83d\udccc Escribe y selecciona un lugar de Google\n                                        Maps\n                                    <\/p>\n                                <\/div>\n\n                                <div class=\"grid grid-cols-1 lg:grid-cols-2 gap-4\">\n                                    <div>\n                                        <label class=\"form-label\">Lugar \/ Venue<\/label>\n                                        <input name=\"lugar\" id=\"lugar\" class=\"form-input\"\n                                            placeholder=\"Ej: Teatro Manuel Doblado\" \/>\n                                        <p class=\"text-xs text-gray-600 mt-1\">Nombre espec\u00edfico del lugar donde se\n                                            realiza\n                                            el evento<\/p>\n                                    <\/div>\n\n                                    <div>\n                                        <label class=\"form-label\">Ciudad (se completa autom\u00e1ticamente)<\/label>\n                                        <input name=\"ciudad\" id=\"ciudad\" type=\"text\" class=\"form-input\" readonly \/>\n                                        <p class=\"text-xs text-gray-600 mt-1\">Se llena autom\u00e1ticamente al seleccionar la\n                                            ubicaci\u00f3n<\/p>\n                                    <\/div>\n                                <\/div>\n                            <\/div>\n\n                            <input type=\"hidden\" id=\"latitud\" name=\"latitud\" \/>\n                            <input type=\"hidden\" id=\"longitud\" name=\"longitud\" \/>\n                            <input type=\"hidden\" id=\"place_id\" name=\"place_id\" \/>\n                            <input type=\"hidden\" id=\"ciudad_key\" name=\"ciudad_key\" \/>\n                            <input type=\"hidden\" id=\"ciudad_label\" name=\"ciudad_label\" \/>\n                            <input type=\"hidden\" id=\"ciudad_place_id\" name=\"ciudad_place_id\" \/>\n\n                            <!-- Secci\u00f3n: Fechas y Horarios -->\n                            <div class=\"form-section bg-purple-50 p-5 rounded-lg border-2 border-purple-300 shadow-sm\">\n                                <h3 class=\"text-lg font-bold text-purple-800 mb-4 pb-2 border-b-2 border-purple-300\">\ud83d\udcc5\n                                    Fechas y Horarios<\/h3>\n\n                                <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n                                    <div>\n                                        <label class=\"form-label\">Fecha de inicio *<\/label>\n                                        <input name=\"fecha_inicio\" id=\"fecha_inicio\" type=\"date\" class=\"form-input\"\n                                            required \/>\n                                    <\/div>\n                                    <div>\n                                        <label class=\"form-label\">Fecha de fin<\/label>\n                                        <input name=\"fecha_fin\" id=\"fecha_fin\" type=\"date\" class=\"form-input\" \/>\n                                        <p class=\"text-xs text-gray-600 mt-1\">Opcional, para eventos de varios d\u00edas<\/p>\n                                    <\/div>\n                                <\/div>\n\n                                <div class=\"grid grid-cols-1 md:grid-cols-3 gap-4\">\n                                    <div>\n                                        <label class=\"form-label\">Hora de inicio<\/label>\n                                        <input name=\"hora\" id=\"hora\" type=\"time\" class=\"form-input\" \/>\n                                    <\/div>\n                                    <div>\n                                        <label class=\"form-label\">Hora de apertura de puerta<\/label>\n                                        <input name=\"hora_apertura_puerta\" id=\"hora_apertura_puerta\" type=\"time\"\n                                            class=\"form-input\" \/>\n                                    <\/div>\n                                    <div>\n                                        <label class=\"form-label\">Hora de cierre<\/label>\n                                        <input name=\"hora_cierre\" id=\"hora_cierre\" type=\"time\" class=\"form-input\" \/>\n                                    <\/div>\n                                <\/div>\n                            <\/div>\n\n                            <!-- Secci\u00f3n: Publicaci\u00f3n (visibilidad) -->\n                            <div class=\"form-section bg-indigo-50 p-5 rounded-lg border-2 border-indigo-300 shadow-sm\">\n                                <h3 class=\"text-lg font-bold text-indigo-800 mb-4 pb-2 border-b-2 border-indigo-300\">\ud83d\udc40\n                                    Publicaci\u00f3n<\/h3>\n\n                                <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n                                    <div>\n                                        <label class=\"form-label\">Visible desde *<\/label>\n                                        <input name=\"publicar_desde\" id=\"publicar_desde\" type=\"date\" class=\"form-input\"\n                                            required \/>\n                                        <p class=\"text-xs text-gray-600 mt-1\">\ud83d\udccc Desde qu\u00e9 d\u00eda quieres que aparezca en\n                                            la app<\/p>\n                                    <\/div>\n\n                                    <div>\n                                        <label class=\"form-label\">Visible hasta<\/label>\n                                        <input name=\"publicar_hasta\" id=\"publicar_hasta\" type=\"date\"\n                                            class=\"form-input\" \/>\n                                        <p class=\"text-xs text-gray-600 mt-1\">\ud83d\udccc Si lo dejas vac\u00edo, se usar\u00e1 la fecha\n                                            fin del evento (o inicio si no hay fin)<\/p>\n                                    <\/div>\n                                <\/div>\n\n                                <div class=\"mt-4 p-3 rounded-lg bg-white border border-indigo-200\">\n                                    <div class=\"text-sm text-gray-700\">\n                                        <b>Estimaci\u00f3n:<\/b> <span id=\"quote-dias\">&#8211;<\/span> d\u00edas \u2022 <b>Total:<\/b> <span\n                                            id=\"quote-total\">&#8211;<\/span>\n                                    <\/div>\n                                    <div class=\"text-xs text-gray-600 mt-1\" id=\"quote-rango\">\n                                        Rango de cobro: &#8211;\n                                    <\/div>\n                                    <div class=\"text-xs text-gray-500 mt-1\">\n                                        *C\u00e1lculo inclusivo: el \u00faltimo d\u00eda <b>S\u00cd<\/b> cuenta.\n                                    <\/div>\n                                <\/div>\n                            <\/div>\n\n                            <!-- Secci\u00f3n: Costo y Boletos -->\n                            <div class=\"form-section bg-yellow-50 p-5 rounded-lg border-2 border-yellow-300 shadow-sm\">\n                                <h3 class=\"text-lg font-bold text-yellow-800 mb-4 pb-2 border-b-2 border-yellow-300\">\ud83d\udcb0\n                                    Costo y Boletos<\/h3>\n\n                                <div class=\"grid grid-cols-1 lg:grid-cols-2 gap-4\">\n                                    <div>\n                                        <label class=\"form-label\">Costo \/ Precio<\/label>\n                                        <input name=\"costo\" id=\"costo\" type=\"text\" class=\"form-input\"\n                                            placeholder=\"Ej: Gratis, $100, $50-$200\" \/>\n                                        <p class=\"text-xs text-gray-600 mt-1\">Indica el costo o rango de precios<\/p>\n                                    <\/div>\n\n                                    <div>\n                                        <label class=\"form-label\">\u00bfD\u00f3nde comprar boletos?<\/label>\n                                        <input name=\"boletos\" id=\"boletos\" class=\"form-input\"\n                                            placeholder=\"Ej: Ticketmaster, taquilla del lugar, www.ejemplo.com\" \/>\n                                        <p class=\"text-xs text-gray-600 mt-1\">URL o lugar donde conseguir boletos<\/p>\n                                    <\/div>\n                                <\/div>\n                            <\/div>\n\n                            <!-- Botones de acci\u00f3n -->\n                            <div\n                                class=\"flex flex-col sm:flex-row gap-4 pt-6 border-t-2 border-gray-200 max-w-4xl mx-auto\">\n                                <button type=\"submit\" class=\"btn-primary flex-1\" id=\"btn-guardar\">\n                                    \ud83d\udcbe Guardar Borrador\n                                <\/button>\n                                <button type=\"button\" onclick=\"enviarARevision()\"\n                                    class=\"btn-primary flex-1 bg-green-600 hover:bg-green-700\" id=\"btn-enviar\">\n                                    \u2705 Enviar a Revisi\u00f3n\n                                <\/button>\n                                <button type=\"button\" onclick=\"cancelarEdicion()\" class=\"btn-secondary sm:flex-none\">\n                                    \u274c Cancelar\n                                <\/button>\n                            <\/div>\n                        <\/form>\n                    <\/div>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <script>\n            const SUPABASE_URL = \"https:\/\/mipnxpjsziezxnmghtcl.supabase.co\";\n            const SUPABASE_ANON = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im1pcG54cGpzemllenhubWdodGNsIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDYyMTQ3OTUsImV4cCI6MjA2MTc5MDc5NX0.NgvIMIUWacovixfPR6TqUiaaO8ba3dZIwWYTbj2LoD8\";\n            const BUCKET_NAME = \"imagenes\";\n\n            const sb = supabase.createClient(SUPABASE_URL, SUPABASE_ANON);\n            let currentUser = null;\n            let misEventosGlobal = [];\n            let eventoEditando = null;\n            let autocompleteGoogle = null;\n            let PRICE_PER_DAY = null;\n            let interesesPermitidos = []; \/\/ array de ids v\u00e1lidos (como string)\n            let interesesMapNombre = {};   \/\/ id \u2192 nombre para display\n            let subcategoriasEv = {};     \/\/ keyed by interes_id\n\n            function parseKeywordsInput(raw) {\n                return String(raw || '')\n                    .split(',')\n                    .map(token => token.trim())\n                    .filter(Boolean);\n            }\n\n            function moneyMXN(n) {\n                return new Intl.NumberFormat('es-MX', { style: 'currency', currency: 'MXN' }).format(n);\n            }\n\n            async function cargarPrecioEvento() {\n                if (PRICE_PER_DAY !== null) return PRICE_PER_DAY;\n                try {\n                    const { data, error } = await sb.rpc('get_event_price_per_day_mxn');\n                    if (error) {\n                        console.error('\u274c Error llamando RPC get_event_price_per_day_mxn:', error);\n                        console.error('Detalles:', error.message, error.details, error.hint);\n                        PRICE_PER_DAY = null;\n                        return null;\n                    }\n\n                    if (data === null || data === undefined) {\n                        PRICE_PER_DAY = null;\n                        return null;\n                    }\n\n                    PRICE_PER_DAY = Number(data);\n                    return PRICE_PER_DAY;\n                } catch (e) {\n                    console.error('\u274c Excepci\u00f3n al cargar precio:', e);\n                    return null;\n                }\n            }\n\n            \/\/ ==================== AUTH ====================\n            async function checkAuth() {\n                const { data: { session } } = await sb.auth.getSession();\n                if (session ? session.user : false) {\n                    currentUser = session.user;\n                    mostrarApp();\n                    await manejarRetornoPagoEvento();\n                } else {\n                    mostrarAuth();\n                }\n            }\n\n            function mostrarAuth() {\n                document.getElementById('auth-screen').classList.remove('hidden');\n                document.getElementById('app-screen').classList.add('hidden');\n            }\n\n            function mostrarApp() {\n                document.getElementById('auth-screen').classList.add('hidden');\n                document.getElementById('app-screen').classList.remove('hidden');\n                document.getElementById('user-email').textContent = currentUser.email;\n                cargarIntereses();\n                cargarMisEventos();\n                mostrarTab('lista');\n            }\n\n            function toggleAuthMode() {\n                document.getElementById('login-form').classList.toggle('hidden');\n                document.getElementById('signup-form').classList.toggle('hidden');\n            }\n\n            async function handleLogin() {\n                const email = document.getElementById('login-email').value.trim();\n                const password = document.getElementById('login-password').value;\n\n                if (!email || !password) {\n                    mostrarMensajeAuth('Por favor completa todos los campos', 'error');\n                    return;\n                }\n\n                try {\n                    const { data, error } = await sb.auth.signInWithPassword({ email, password });\n                    if (error) throw error;\n                    currentUser = data.user;\n                    mostrarApp();\n                    await manejarRetornoPagoEvento();\n                } catch (error) {\n                    mostrarMensajeAuth(error.message, 'error');\n                }\n            }\n\n            async function handleSignup() {\n                const email = document.getElementById('signup-email').value.trim();\n                const password = document.getElementById('signup-password').value;\n\n                if (!email || !password) {\n                    mostrarMensajeAuth('Por favor completa todos los campos', 'error');\n                    return;\n                }\n\n                if (password.length < 6) {\n                    mostrarMensajeAuth('La contrase\u00f1a debe tener al menos 6 caracteres', 'error');\n                    return;\n                }\n\n                try {\n                    const { data, error } = await sb.auth.signUp({ email, password });\n                    if (error) throw error;\n                    mostrarMensajeAuth('Cuenta creada. Revisa tu email para confirmar.', 'success');\n                    setTimeout(() => toggleAuthMode(), 2000);\n                } catch (error) {\n                    mostrarMensajeAuth(error.message, 'error');\n                }\n            }\n\n            async function handleLogout() {\n                await sb.auth.signOut();\n                currentUser = null;\n                mostrarAuth();\n            }\n\n            function mostrarMensajeAuth(mensaje, tipo) {\n                const div = document.getElementById('auth-message');\n                div.textContent = mensaje;\n                div.className = `mt-4 p-3 rounded-lg text-sm ${tipo === 'error' ? 'bg-red-100 text-red-700' : 'bg-green-100 text-green-700'}`;\n                div.classList.remove('hidden');\n                setTimeout(() => div.classList.add('hidden'), 5000);\n            }\n\n            \/\/ ==================== TABS ====================\n            function mostrarTab(tab) {\n                document.querySelectorAll('.tab-btn').forEach(btn => {\n                    btn.classList.remove('bg-blue-600', 'text-white');\n                    btn.classList.add('bg-gray-200', 'text-gray-700');\n                    if (btn.dataset.tab === tab) {\n                        btn.classList.remove('bg-gray-200', 'text-gray-700');\n                        btn.classList.add('bg-blue-600', 'text-white');\n                    }\n                });\n\n                document.getElementById('tab-lista').classList.add('hidden');\n                document.getElementById('tab-nuevo').classList.add('hidden');\n                document.getElementById(`tab-${tab}`).classList.remove('hidden');\n\n                if (tab === 'lista') {\n                    cargarMisEventos();\n                } else if (tab === 'nuevo') {\n                    \/\/ \u2705 Solo resetea si NO est\u00e1s editando\n                    if (!eventoEditando) resetFormulario();\n                }\n            }\n\n            \/\/ ==================== INTERESES ====================\n            function toggleSubcatsEv(checkbox, interesId) {\n                const panel = checkbox.closest('div').querySelector(`.ev-subcats[data-interes=\"${interesId}\"]`);\n                if (!panel) return;\n                if (checkbox.checked) {\n                    panel.style.display = 'flex';\n                } else {\n                    panel.style.display = 'none';\n                    panel.querySelectorAll('input[type=\"checkbox\"]').forEach(cb => cb.checked = false);\n                }\n            }\n\n            async function cargarIntereses(selectedIds = [], selectedSubIds = []) {\n                try {\n                    const [{ data, error }, { data: subData, error: subError }] = await Promise.all([\n                        sb.from('intereses').select('id, nombre').order('orden', { ascending: true }),\n                        sb.from('subcategorias_intereses').select('id, interes_id, nombre').order('orden', { ascending: true })\n                    ]);\n\n                    if (error) throw error;\n                    if (subError) console.warn('Error al cargar subcategor\u00edas:', subError);\n\n                    const intereses = (data || []).filter(r => r.id && r.nombre);\n                    interesesPermitidos = intereses.map(r => String(r.id));\n                    interesesMapNombre = {};\n                    intereses.forEach(r => { interesesMapNombre[String(r.id)] = String(r.nombre).trim(); });\n\n                    subcategoriasEv = {};\n                    (subData || []).forEach(s => {\n                        if (!subcategoriasEv[s.interes_id]) subcategoriasEv[s.interes_id] = [];\n                        subcategoriasEv[s.interes_id].push(s);\n                    });\n\n                    const container = document.getElementById('interes');\n                    if (!container) return;\n\n                    const normIds = selectedIds.map(x => Number(x));\n                    const normSubIds = selectedSubIds.map(x => Number(x));\n\n                    container.innerHTML = intereses.map(r => {\n                        const isChecked = normIds.includes(Number(r.id));\n                        const subs = subcategoriasEv[r.id] || [];\n                        const subsHtml = subs.length === 0 ? '' : `\n                            <div class=\"ev-subcats\" data-interes=\"${r.id}\" style=\"display:${isChecked ? 'flex' : 'none'}; flex-direction:column; gap:2px; padding-left:24px; margin-top:2px;\">\n                                ${subs.map(s => `\n                                    <label style=\"display:flex; align-items:center; gap:6px; cursor:pointer; padding:1px 4px; border-radius:4px;\">\n                                        <input type=\"checkbox\" class=\"subcat-cb\" value=\"${s.id}\" ${normSubIds.includes(Number(s.id)) ? 'checked' : ''} style=\"width:13px; height:13px; cursor:pointer;\" \/>\n                                        <span style=\"font-size:13px; color:#555;\">${String(s.nombre).trim()}<\/span>\n                                    <\/label>\n                                `).join('')}\n                            <\/div>`;\n                        return `\n                            <div>\n                                <label style=\"display:flex; align-items:center; gap:8px; cursor:pointer; padding:2px 4px; border-radius:4px;\">\n                                    <input type=\"checkbox\" class=\"interes-cb\" value=\"${r.id}\" ${isChecked ? 'checked' : ''}\n                                        onchange=\"toggleSubcatsEv(this, ${r.id})\"\n                                        style=\"width:16px; height:16px; cursor:pointer;\" \/>\n                                    <span style=\"font-size:14px\">${String(r.nombre).trim()}<\/span>\n                                    ${subs.length > 0 ? `<span style=\"font-size:11px; color:#aaa;\">(${subs.length})<\/span>` : ''}\n                                <\/label>\n                                ${subsHtml}\n                            <\/div>`;\n                    }).join('');\n                } catch (error) {\n                    console.error('Error cargando intereses:', error);\n                }\n            }\n\n            function esInteresPermitido(idInteres) {\n                const valor = String(idInteres || '').trim();\n                if (!valor) return false;\n                return interesesPermitidos.includes(valor);\n            }\n\n            \/\/ ==================== MIS EVENTOS ====================\n            async function cargarMisEventos() {\n                try {\n                    const { data, error } = await sb\n                        .from('eventos_submissions')\n                        .select('*, submission_intereses(interes_id, intereses(nombre))')\n                        .eq('creado_por', currentUser.id)\n                        .order('creado_en', { ascending: false });\n\n                    if (error) throw error;\n                    misEventosGlobal = data || [];\n                    renderizarMisEventos();\n                } catch (error) {\n                    console.error('Error cargando eventos:', error);\n                    document.getElementById('lista-eventos').innerHTML =\n                        `<div class=\"bg-red-50 border border-red-200 rounded-lg p-4 text-red-700\">\n            Error al cargar eventos: ${error.message}\n          <\/div>`;\n                }\n            }\n\n            function filtrarMisEventos() {\n                const search = document.getElementById('search-input').value.toLowerCase();\n                const filtrados = misEventosGlobal.filter(e =>\n                    e.nombre.toLowerCase().includes(search)\n                );\n                renderizarMisEventos(filtrados);\n            }\n\n            function renderizarMisEventos(eventos = misEventosGlobal) {\n                const container = document.getElementById('lista-eventos');\n\n                if (!eventos || eventos.length === 0) {\n                    container.innerHTML = `\n          <div class=\"bg-gray-50 border border-gray-200 rounded-lg p-8 text-center\">\n            <p class=\"text-gray-600 mb-4\">No tienes eventos registrados a\u00fan<\/p>\n            <button onclick=\"mostrarTab('nuevo')\" class=\"btn-primary\">Crear mi primer evento<\/button>\n          <\/div>`;\n                    return;\n                }\n\n                container.innerHTML = eventos.map(evento => {\n                    const puedeEditar = ['borrador', 'rechazado'].includes(evento.estado);\n                    const puedeEnviar = ['borrador', 'rechazado'].includes(evento.estado);\n                    const puedeEliminar = evento.estado === 'borrador';\n                    const puedePagar = (evento.estado === 'aprobado');\n                    const imagenUrl = evento.imagen_path\n                        ? `${SUPABASE_URL}\/storage\/v1\/object\/public\/${BUCKET_NAME}\/${evento.imagen_path}`\n                        : `${SUPABASE_URL}\/storage\/v1\/object\/public\/${BUCKET_NAME}\/defaults\/placeholder.jpeg`;\n\n\n                    return `\n          <div class=\"bg-white rounded-lg shadow-md p-4 flex gap-4\">\n            <img decoding=\"async\" src=\"${imagenUrl}\" class=\"w-32 h-32 object-cover rounded-lg\" alt=\"${evento.nombre}\" \/>\n            <div class=\"flex-1\">\n              <div class=\"flex justify-between items-start mb-2\">\n                <h3 class=\"text-lg font-bold text-gray-800\">${evento.nombre}<\/h3>\n                <span class=\"badge badge-${evento.estado}\">${formatearEstado(evento.estado)}<\/span>\n              <\/div>\n              <p class=\"text-sm text-gray-600 mb-1\">${(evento.submission_intereses || []).map(x => x.intereses?.nombre).filter(Boolean).join(', ') || 'Sin inter\u00e9s'}<\/p>\n              <p class=\"text-sm text-gray-600 mb-1\">\n                \ud83d\udcc5 ${formatearFecha(evento.fecha_inicio)}${evento.fecha_fin ? ' - ' + formatearFecha(evento.fecha_fin) : ''}\n              <\/p>\n              <p class=\"text-sm text-gray-600 mb-1\">\ud83d\udd52 ${formatearHora(evento.hora) || 'Sin hora'}${evento.hora_apertura_puerta ? ' \u00b7 Puertas: ' + formatearHora(evento.hora_apertura_puerta) : ''}${evento.hora_cierre ? ' \u00b7 Cierre: ' + formatearHora(evento.hora_cierre) : ''}<\/p>\n              <p class=\"text-sm text-gray-600 mb-2\">\ud83d\udccd ${evento.ciudad_label || evento.ciudad || 'Sin ubicaci\u00f3n'}<\/p>\n              ${evento.estado === 'rechazado' ? (evento.razon_rechazo ? `<p class=\"text-sm text-red-600 mb-2\">\u274c Rechazado: ${evento.razon_rechazo}<\/p>` : '') : ''}\n              <div class=\"flex gap-2 mt-3\">\n                ${puedeEditar ? `<button onclick=\"editarEvento(${evento.id})\" class=\"text-sm bg-blue-600 text-white px-3 py-1 rounded hover:bg-blue-700\">Editar<\/button>` : ''}\n                ${puedeEliminar ? `<button onclick=\"eliminarEvento(${evento.id})\" class=\"text-sm bg-red-600 text-white px-3 py-1 rounded hover:bg-red-700\">Eliminar<\/button>` : ''}\n                ${puedeEnviar ? `<button onclick=\"enviarDirectoARevision(${evento.id})\" class=\"text-sm bg-green-600 text-white px-3 py-1 rounded hover:bg-green-700\">Enviar a Revisi\u00f3n<\/button>` : ''}\n                ${puedePagar ? `<button onclick=\"pagarEvento(${evento.id})\" class=\"text-sm bg-purple-600 text-white px-3 py-1 rounded hover:bg-purple-700\">\ud83d\udcb3 Pagar y publicar<\/button>` : ''}\n              <\/div>\n            <\/div>\n          <\/div>`;\n                }).join('');\n            }\n\n            function formatearEstado(estado) {\n                const estados = {\n                    'borrador': 'Borrador',\n                    'enviado': 'Enviado',\n                    'en_revision': 'En Revisi\u00f3n',\n                    'aprobado': 'Aprobado',\n                    'rechazado': 'Rechazado',\n                    'publicado': 'Publicado'\n                };\n                return estados[estado] || estado;\n            }\n\n            function formatearFecha(fecha) {\n                if (!fecha) return '';\n                return new Date(fecha).toLocaleDateString('es-MX', {\n                    year: 'numeric',\n                    month: 'short',\n                    day: 'numeric'\n                });\n            }\n\n            \/\/ ==================== FORMULARIO ====================\n            function resetFormulario() {\n                eventoEditando = null;\n                document.getElementById('form-titulo').textContent = 'Nuevo Evento';\n                document.getElementById('evento-form').reset();\n                document.getElementById('evento-id').value = '';\n                document.getElementById('imagen-preview').classList.add('hidden');\n                document.getElementById('btn-guardar').textContent = 'Guardar Borrador';\n                document.getElementById('btn-enviar').classList.remove('hidden');\n                document.getElementById('imagen-file').value = '';\n\n                \/\/ defaults de publicaci\u00f3n\n                const hoy = new Date();\n                const yyyy = hoy.getFullYear();\n                const mm = String(hoy.getMonth() + 1).padStart(2, '0');\n                const dd = String(hoy.getDate()).padStart(2, '0');\n                const hoyStr = `${yyyy}-${mm}-${dd}`;\n\n                document.getElementById('publicar_desde').value = hoyStr;\n                document.getElementById('publicar_hasta').value = '';\n\n                actualizarCotizacionUI();\n            }\n\n            async function editarEvento(eventoId) {\n                try {\n                    const { data, error } = await sb\n                        .from('eventos_submissions')\n                        .select('*, submission_intereses(interes_id), submission_subcategorias(subcategoria_id)')\n                        .eq('id', eventoId)\n                        .single();\n\n                    if (error) throw error;\n\n                    if (!['borrador', 'rechazado'].includes(data.estado)) {\n                        alert('Este evento no se puede editar en este estado.');\n                        return;\n                    }\n\n                    eventoEditando = data;\n                    document.getElementById('form-titulo').textContent = 'Editar Evento';\n                    document.getElementById('evento-id').value = data.id;\n\n                    \/\/ Llenar formulario\n                    document.getElementById('nombre').value = data.nombre || '';\n                    document.getElementById('descripcion').value = data.descripcion || '';\n                    document.getElementById('keywords').value = Array.isArray(data.keywords) ? data.keywords.join(', ') : '';\n                    await cargarIntereses(\n                        (data.submission_intereses || []).map(x => x.interes_id),\n                        (data.submission_subcategorias || []).map(x => x.subcategoria_id)\n                    );\n                    document.getElementById('ubicacion').value = data.ubicacion || '';\n                    document.getElementById('lugar').value = data.lugar || '';\n                    document.getElementById('ciudad').value = data.ciudad_label || data.ciudad || '';\n                    document.getElementById('latitud').value = data.latitud || '';\n                    document.getElementById('longitud').value = data.longitud || '';\n                    document.getElementById('place_id').value = data.place_id || '';\n                    document.getElementById('ciudad_key').value = data.ciudad_key || '';\n                    document.getElementById('ciudad_label').value = data.ciudad_label || '';\n                    document.getElementById('ciudad_place_id').value = data.ciudad_place_id || '';\n                    document.getElementById('fecha_inicio').value = data.fecha_inicio || '';\n                    document.getElementById('fecha_fin').value = data.fecha_fin || '';\n                    document.getElementById('hora').value = formatearHora(data.hora);\n                    document.getElementById('hora_apertura_puerta').value = formatearHora(data.hora_apertura_puerta);\n                    document.getElementById('hora_cierre').value = formatearHora(data.hora_cierre);\n                    document.getElementById('costo').value = data.costo || '';\n                    document.getElementById('boletos').value = data.boletos || '';\n                    document.getElementById('publicar_desde').value = data.publicar_desde || data.fecha_inicio || '';\n                    document.getElementById('publicar_hasta').value = data.publicar_hasta || data.fecha_fin || '';\n                    actualizarCotizacionUI();\n\n                    if (data.imagen_path) {\n                        const imagenUrl = `${SUPABASE_URL}\/storage\/v1\/object\/public\/${BUCKET_NAME}\/${data.imagen_path}`;\n                        document.getElementById('imagen-preview-img').src = imagenUrl;\n                        document.getElementById('imagen-preview').classList.remove('hidden');\n                    } else {\n                        document.getElementById('imagen-preview').classList.add('hidden');\n                    }\n\n                    mostrarTab('nuevo');\n                } catch (error) {\n                    alert('Error al cargar evento: ' + error.message);\n                }\n            }\n\n\n            function formatearHora(hora) {\n                if (!hora) return '';\n                return hora.substring(0, 5); \/\/ HH:MM\n            }\n\n            function cancelarEdicion() {\n                resetFormulario();\n                mostrarTab('lista');\n            }\n\n            async function eliminarEvento(eventoId) {\n                if (!confirm('\u00bfEliminar este evento borrador?')) return;\n\n                try {\n                    const { error } = await sb\n                        .from('eventos_submissions')\n                        .delete()\n                        .eq('id', eventoId)\n                        .eq('creado_por', currentUser.id);\n\n                    if (error) throw error;\n                    alert('Evento eliminado correctamente');\n                    cargarMisEventos();\n                } catch (error) {\n                    alert('Error al eliminar: ' + error.message);\n                }\n            }\n\n            async function enviarDirectoARevision(eventoId) {\n                if (!confirm('\u00bfEnviar este evento a revisi\u00f3n?')) return;\n\n                try {\n                    const { data, error } = await sb.rpc('enviar_evento_a_revision', {\n                        p_submission_id: eventoId\n                    });\n\n                    if (error) throw error;\n                    if (data !== 'OK') throw new Error(data);\n\n                    alert('\u2705 Evento enviado a revisi\u00f3n');\n                    cargarMisEventos();\n                } catch (error) {\n                    alert('Error: ' + (error.message || error));\n                }\n            }\n\n            async function guardarEventoSoloDatos() {\n                const eventoId = document.getElementById('evento-id').value;\n                const formData = new FormData(document.getElementById('evento-form'));\n\n                const fechaInicio = formData.get('fecha_inicio') || null;\n                const fechaFin = formData.get('fecha_fin') || null;\n                const publicarDesde = formData.get('publicar_desde') || null;\n                const publicarHastaCapturada = formData.get('publicar_hasta') || null;\n                const publicarHastaEfectiva = resolvePublicarHasta({\n                    publicarDesde,\n                    publicarHasta: publicarHastaCapturada,\n                    fechaInicio,\n                    fechaFin\n                });\n\n                const interesesSeleccionados = [...document.querySelectorAll('.interes-cb:checked')].map(cb => parseInt(cb.value));\n                const subcategoriasSeleccionadas = [...document.querySelectorAll('.subcat-cb:checked')].map(cb => parseInt(cb.value));\n                const data = {\n                    nombre: formData.get('nombre'),\n                    descripcion: formData.get('descripcion'),\n                    keywords: parseKeywordsInput(formData.get('keywords')),\n                    ubicacion: formData.get('ubicacion'),\n                    lugar: formData.get('lugar'),\n                    ciudad: formData.get('ciudad'), \/\/ opcional\n                    ciudad_key: formData.get('ciudad_key'),\n                    ciudad_label: formData.get('ciudad_label'),\n                    ciudad_place_id: formData.get('ciudad_place_id'),\n                    latitud: parseFloat(formData.get('latitud')) || null,\n                    longitud: parseFloat(formData.get('longitud')) || null,\n                    place_id: formData.get('place_id'),\n                    fecha_inicio: fechaInicio,\n                    fecha_fin: fechaFin,\n                    hora: formData.get('hora') || null,\n                    hora_apertura_puerta: formData.get('hora_apertura_puerta') || null,\n                    hora_cierre: formData.get('hora_cierre') || null,\n                    costo: formData.get('costo'),\n                    boletos: formData.get('boletos'),\n                    publicar_desde: publicarDesde,\n                    publicar_hasta: publicarHastaEfectiva\n                };\n\n                if (interesesSeleccionados.length === 0) {\n                    alert('\u26a0\ufe0f Inter\u00e9s requerido\\n\\nSelecciona al menos un inter\u00e9s de la lista.');\n                    throw new Error('VALIDATION');\n                }\n\n                \/\/ Validaci\u00f3n de campos obligatorios con mensajes espec\u00edficos\n                if (!data.nombre || !data.nombre.trim()) {\n                    alert('\u26a0\ufe0f Campo requerido\\n\\nEl nombre del evento es obligatorio.');\n                    throw new Error('VALIDATION');\n                }\n                if (!data.descripcion || !data.descripcion.trim()) {\n                    alert('\u26a0\ufe0f Campo requerido\\n\\nLa descripci\u00f3n del evento es obligatoria.');\n                    throw new Error('VALIDATION');\n                }\n                if (!data.ubicacion || !data.ubicacion.trim()) {\n                    alert('\u26a0\ufe0f Ubicaci\u00f3n requerida\\n\\nEscribe y selecciona una ubicaci\u00f3n v\u00e1lida del autocompletado de Google Maps.');\n                    throw new Error('VALIDATION');\n                }\n                if (!data.latitud || !data.longitud || !data.ciudad_key || !data.ciudad_label || !data.ciudad_place_id) {\n                    alert('\u26a0\ufe0f Ubicaci\u00f3n incompleta\\n\\nDebes seleccionar la ubicaci\u00f3n desde el autocompletado de Google Maps para obtener coordenadas y ciudad correctamente.\\n\\nBorra el campo y vuelve a escribir para ver las sugerencias.');\n                    throw new Error('VALIDATION');\n                }\n                if (!data.fecha_inicio) {\n                    alert('\u26a0\ufe0f Campo requerido\\n\\nLa fecha de inicio es obligatoria.');\n                    throw new Error('VALIDATION');\n                }\n                if (!data.publicar_desde) {\n                    alert('\u26a0\ufe0f Campo requerido\\n\\nLa fecha \"Visible desde\" es obligatoria.');\n                    throw new Error('VALIDATION');\n                }\n\n                if (publicarHastaCapturada) {\n                    if (data.publicar_desde) {\n                        if (publicarHastaCapturada < data.publicar_desde) {\n                            alert('\u26a0\ufe0f Fechas inv\u00e1lidas\\n\\nLa fecha \"Visible hasta\" no puede ser menor que \"Visible desde\".');\n                            throw new Error('VALIDATION');\n                        }\n                    }\n                }\n\n                \/\/ Confirmaci\u00f3n si no hay imagen\n                const fileInput = document.getElementById('imagen-file');\n                const archivoSeleccionado = fileInput.files ? fileInput.files[0] : null;\n                const tieneImagenExistente = !!(eventoEditando?.imagen_path);\n                if (!(archivoSeleccionado || tieneImagenExistente)) {\n                    const continuar = confirm('\u26a0\ufe0f Sin imagen\\n\\nEste evento no tiene imagen adjunta. Los eventos con imagen tienen mayor visibilidad.\\n\\n\u00bfDeseas continuar sin imagen?');\n                    if (!continuar) throw new Error('VALIDATION');\n                }\n\n                let submissionId = eventoId ? parseInt(eventoId) : null;\n\n                if (submissionId) {\n                    const { error } = await sb\n                        .from('eventos_submissions')\n                        .update(data)\n                        .eq('id', submissionId)\n                        .eq('creado_por', currentUser.id);\n\n                    if (error) throw error;\n                } else {\n                    data.creado_por = currentUser.id;\n                    data.estado = 'borrador'; \/\/ siempre al crear\n                    const { data: newData, error } = await sb\n                        .from('eventos_submissions')\n                        .insert([data])\n                        .select('id')\n                        .single();\n\n                    if (error) throw error;\n                    submissionId = newData.id;\n                }\n\n                \/\/ Actualizar junction table de intereses\n                await sb.from('submission_intereses').delete().eq('submission_id', submissionId);\n                const interesRows = interesesSeleccionados.map(interes_id => ({ submission_id: submissionId, interes_id }));\n                const { error: insErr } = await sb.from('submission_intereses').insert(interesRows);\n                if (insErr) throw insErr;\n\n                \/\/ Actualizar junction table de subcategor\u00edas\n                await sb.from('submission_subcategorias').delete().eq('submission_id', submissionId);\n                if (subcategoriasSeleccionadas.length > 0) {\n                    const subRows = subcategoriasSeleccionadas.map(subcategoria_id => ({ submission_id: submissionId, subcategoria_id }));\n                    const { error: subInsErr } = await sb.from('submission_subcategorias').insert(subRows);\n                    if (subInsErr) throw subInsErr;\n                }\n\n                if (archivoSeleccionado) {\n                    await subirImagen(archivoSeleccionado, submissionId);\n                }\n\n                return submissionId;\n            }\n\n            async function enviarARevisionRPC(submissionId) {\n                const { data, error } = await sb.rpc('enviar_evento_a_revision', {\n                    p_submission_id: parseInt(submissionId)\n                });\n                if (error) throw error;\n                if (data !== 'OK') throw new Error(data);\n            }\n\n            \/\/ ==================== SUBMIT FORMULARIO ====================\n            document.getElementById('evento-form').addEventListener('submit', async (e) => {\n                e.preventDefault();\n                try {\n                    await guardarEventoSoloDatos();\n                    alert('\u2705 Evento guardado como borrador');\n                    resetFormulario();\n                    mostrarTab('lista');\n                    cargarMisEventos();\n                } catch (err) {\n                    const errMsg = err ? err.message : null;\n                    if (!err || errMsg !== 'VALIDATION') alert('Error al guardar: ' + (errMsg || err));\n                }\n            });\n\n            async function enviarARevision() {\n                \/\/ 1) guarda cambios como borrador\/rechazado (sin tocar estado)\n                const submissionId = await guardarEventoSoloDatos();\n                \/\/ 2) cambia estado por RPC\n                await enviarARevisionRPC(submissionId);\n                alert('\u2705 Evento enviado a revisi\u00f3n');\n                resetFormulario();\n                mostrarTab('lista');\n                cargarMisEventos();\n            }\n\n            \/\/ ==================== SUBIR IMAGEN ====================\n            async function subirImagen(file, submissionId) {\n                try {\n                    console.log('\ud83d\udce4 Iniciando subida de imagen para submission ID:', submissionId);\n\n                    if (!submissionId) {\n                        throw new Error('No se proporcion\u00f3 submissionId');\n                    }\n\n                    if (!validarImagen(file)) {\n                        console.warn('\u26a0\ufe0f Imagen no v\u00e1lida');\n                        return;\n                    }\n\n                    \/\/ Comprimir\n                    console.log('\ud83d\udd04 Comprimiendo imagen...');\n                    const fileComprimido = await compressToJpg(file, {\n                        maxWidth: 1280,\n                        maxHeight: 1280,\n                        quality: 0.75\n                    });\n                    console.log('\u2705 Imagen comprimida:', fileComprimido.size, 'bytes');\n\n                    \/\/ Path: eventos_submissions\/<uid>\/evento-<id>.jpg\n                    const path = `eventos_submissions\/${currentUser.id}\/evento-${submissionId}.jpg`;\n                    console.log('\ud83d\udcc1 Ruta de destino:', path);\n\n                    \/\/ Subir a storage\n                    console.log('\u2b06\ufe0f Subiendo a Storage...');\n                    const { data: uploadData, error: uploadError } = await sb.storage\n                        .from(BUCKET_NAME)\n                        .upload(path, fileComprimido, {\n                            contentType: 'image\/jpeg',\n                            upsert: true\n                        });\n\n                    if (uploadError) {\n                        console.error('\u274c Error al subir a Storage:', uploadError);\n                        throw uploadError;\n                    }\n                    console.log('\u2705 Subida exitosa a Storage:', uploadData);\n\n                    \/\/ Actualizar registro con el path\n                    console.log('\ud83d\udcbe Actualizando imagen_path en la base de datos...');\n                    const { data: updateData, error: updateError } = await sb\n                        .from('eventos_submissions')\n                        .update({ imagen_path: path })\n                        .eq('id', parseInt(submissionId))\n                        .eq('creado_por', currentUser.id)\n                        .select();\n\n                    if (updateError) {\n                        console.error('\u274c Error al actualizar imagen_path:', updateError);\n                        throw new Error(`Error al actualizar imagen_path: ${updateError.message}`);\n                    }\n\n                    console.log('\u2705 imagen_path actualizado:', updateData);\n                    console.log('\ud83c\udf89 Proceso completo. Ruta guardada:', path);\n                } catch (error) {\n                    console.error('\u274c Error en subirImagen:', error);\n                    throw error; \/\/ Re-lanzar el error para que guardarEvento lo maneje\n                }\n            }\n\n            function validarImagen(file) {\n                const tipos = [\"image\/jpeg\", \"image\/png\", \"image\/webp\"];\n                if (!tipos.includes(file.type)) {\n                    alert(\"Solo se permiten im\u00e1genes JPG, PNG o WEBP\");\n                    return false;\n                }\n                const maxMB = 8;\n                if (file.size > maxMB * 1024 * 1024) {\n                    alert(`La imagen es muy grande. M\u00e1ximo ${maxMB} MB`);\n                    return false;\n                }\n                return true;\n            }\n\n            function loadImageFromFile(file) {\n                return new Promise((resolve, reject) => {\n                    const url = URL.createObjectURL(file);\n                    const img = new Image();\n                    img.onload = () => {\n                        URL.revokeObjectURL(url);\n                        resolve(img);\n                    };\n                    img.onerror = (e) => {\n                        URL.revokeObjectURL(url);\n                        reject(e);\n                    };\n                    img.src = url;\n                });\n            }\n\n            async function compressToJpg(file, { maxWidth = 1600, maxHeight = 1600, quality = 0.8 } = {}) {\n                const img = await loadImageFromFile(file);\n\n                let { width, height } = img;\n                const ratio = Math.min(maxWidth \/ width, maxHeight \/ height, 1);\n                const newW = Math.round(width * ratio);\n                const newH = Math.round(height * ratio);\n\n                const canvas = document.createElement(\"canvas\");\n                canvas.width = newW;\n                canvas.height = newH;\n                const ctx = canvas.getContext(\"2d\");\n\n                ctx.imageSmoothingEnabled = true;\n                ctx.imageSmoothingQuality = \"high\";\n                ctx.fillStyle = \"#ffffff\";\n                ctx.fillRect(0, 0, newW, newH);\n                ctx.drawImage(img, 0, 0, newW, newH);\n\n                const blob = await new Promise((resolve) => {\n                    canvas.toBlob((b) => resolve(b), \"image\/jpeg\", quality);\n                });\n\n                if (!blob) throw new Error(\"No se pudo comprimir la imagen\");\n                return new File([blob], \"imagen.jpg\", { type: \"image\/jpeg\" });\n            }\n\n            \/\/ ==================== GOOGLE PLACES ====================\n            function initGooglePlaces() {\n                const inputUbicacion = document.getElementById(\"ubicacion\");\n                const mapsPlaces = inputUbicacion\n                    ? (window.google\n                        ? (window.google.maps\n                            ? window.google.maps.places\n                            : null)\n                        : null)\n                    : null;\n\n                if (mapsPlaces) {\n                    autocompleteGoogle = new google.maps.places.Autocomplete(inputUbicacion, {\n                        fields: [\"formatted_address\", \"place_id\", \"geometry\", \"address_components\"],\n                        componentRestrictions: { country: \"mx\" }\n                    });\n\n                    \/\/ Limpiar todos los campos de ubicaci\u00f3n si el usuario edita manualmente\n                    inputUbicacion.addEventListener(\"input\", () => {\n                        document.getElementById(\"latitud\").value = \"\";\n                        document.getElementById(\"longitud\").value = \"\";\n                        document.getElementById(\"place_id\").value = \"\";\n                        document.getElementById(\"ciudad\").value = \"\";\n                        document.getElementById(\"ciudad_key\").value = \"\";\n                        document.getElementById(\"ciudad_label\").value = \"\";\n                        document.getElementById(\"ciudad_place_id\").value = \"\";\n                    });\n\n                    autocompleteGoogle.addListener(\"place_changed\", () => {\n                        const place = autocompleteGoogle.getPlace();\n                        if (!place.geometry) return;\n\n                        inputUbicacion.value = place.formatted_address || '';\n                        document.getElementById(\"latitud\").value = place.geometry.location.lat();\n                        document.getElementById(\"longitud\").value = place.geometry.location.lng();\n                        document.getElementById(\"place_id\").value = place.place_id || '';\n\n                        if (place.address_components) {\n                            const ciudadKey = buildCityKey(place.address_components);\n                            const ciudadLabel = buildCityLabel(place.address_components);\n\n                            document.getElementById(\"ciudad\").value = ciudadLabel;\n                            document.getElementById(\"ciudad_key\").value = ciudadKey;\n                            document.getElementById(\"ciudad_label\").value = ciudadLabel;\n                            document.getElementById(\"ciudad_place_id\").value = place.place_id || '';\n                        }\n                    });\n                }\n            }\n\n            function stripAccents(s) {\n                return (s || \"\").normalize(\"NFD\").replace(\/[\\u0300-\\u036f]\/g, \"\");\n            }\n\n            function slugify(s) {\n                return stripAccents(s)\n                    .toLowerCase()\n                    .trim()\n                    .replace(\/[^a-z0-9]+\/g, \"-\")\n                    .replace(\/-+\/g, \"-\")\n                    .replace(\/^-|-$\/g, \"\");\n            }\n\n            function getComponent(addressComponents, type) {\n                return (addressComponents || []).find(c => (c.types || []).includes(type)) || null;\n            }\n\n            function pickCityName(addressComponents) {\n                const city =\n                    getComponent(addressComponents, \"locality\") ||\n                    getComponent(addressComponents, \"postal_town\") ||\n                    getComponent(addressComponents, \"administrative_area_level_2\");\n                return city ? city.long_name : null;\n            }\n\n            function buildCityKey(addressComponents) {\n                const countryComp = getComponent(addressComponents, \"country\");\n                const stateComp = getComponent(addressComponents, \"administrative_area_level_1\");\n                const country = (countryComp ? countryComp.short_name : null) || \"xx\";\n                const state =\n                    (stateComp ? stateComp.short_name : null)\n                    || (stateComp ? stateComp.long_name : null)\n                    || \"xx\";\n                const cityName = pickCityName(addressComponents) || \"unknown\";\n                return `${slugify(country)}-${slugify(state)}-${slugify(cityName)}`;\n            }\n\n            function buildCityLabel(addressComponents) {\n                const cityName = pickCityName(addressComponents) || \"Ciudad\";\n                const stateComp = getComponent(addressComponents, \"administrative_area_level_1\");\n                const stateShort =\n                    (stateComp ? stateComp.short_name : null)\n                    || (stateComp ? stateComp.long_name : null)\n                    || \"\";\n                return stateShort ? `${cityName} (${stateShort})` : cityName;\n            }\n\n            \/\/ ==================== COTIZACI\u00d3N (D\u00cdAS Y TOTAL) ====================\n            function parseISODate(s) {\n                if (!s) return null;\n                const [y, m, d] = s.split('-').map(Number);\n                return new Date(Date.UTC(y, m - 1, d));\n            }\n\n            function formatISODateDisplay(isoDate) {\n                if (!isoDate) return '-';\n                const [y, m, d] = isoDate.split('-');\n                if (!y || !m || !d) return '-';\n                return `${d}\/${m}\/${y}`;\n            }\n\n            function diffDaysInclusive(aStr, bStr) {\n                const a = parseISODate(aStr);\n                const b = parseISODate(bStr);\n                if (!a || !b) return null;\n                const ms = (b - a);\n                const days = Math.floor(ms \/ (24 * 3600 * 1000)) + 1; \/\/ inclusivo\n                return days;\n            }\n\n            function resolvePublicarHasta({ publicarDesde, publicarHasta, fechaInicio, fechaFin }) {\n                if (publicarHasta) return publicarHasta;\n\n                const fallback = fechaFin || fechaInicio || publicarDesde || null;\n                if (!fallback) return null;\n\n                if (publicarDesde) {\n                    if (fallback < publicarDesde) return publicarDesde;\n                }\n                return fallback;\n            }\n\n            async function actualizarCotizacionUI() {\n                const elPubDesde = document.getElementById('publicar_desde');\n                const elPubHasta = document.getElementById('publicar_hasta');\n                const elFechaInicio = document.getElementById('fecha_inicio');\n                const elFechaFin = document.getElementById('fecha_fin');\n\n                const pubDesde = elPubDesde ? elPubDesde.value : '';\n                const pubHastaInput = elPubHasta ? elPubHasta.value : '';\n\n                const fechaInicio = elFechaInicio ? elFechaInicio.value : '';\n                const fechaFin = elFechaFin ? elFechaFin.value : '';\n                const pubHasta = resolvePublicarHasta({\n                    publicarDesde: pubDesde,\n                    publicarHasta: pubHastaInput,\n                    fechaInicio,\n                    fechaFin\n                });\n\n                const dias = diffDaysInclusive(pubDesde, pubHasta);\n                const diasValidos = Number.isFinite(dias) ? (dias > 0) : false;\n                const elQuoteRango = document.getElementById('quote-rango');\n\n                if (elQuoteRango) {\n                    if (pubDesde) {\n                        if (pubHasta) {\n                            elQuoteRango.textContent = `Rango de cobro: ${formatISODateDisplay(pubDesde)} a ${formatISODateDisplay(pubHasta)}`;\n                        } else {\n                            elQuoteRango.textContent = 'Rango de cobro: -';\n                        }\n                    } else {\n                        elQuoteRango.textContent = 'Rango de cobro: -';\n                    }\n                }\n\n                document.getElementById('quote-dias').textContent = diasValidos ? `${dias}` : '-';\n\n                if (!diasValidos) {\n                    document.getElementById('quote-total').textContent = '-';\n                    return;\n                }\n\n                const price = await cargarPrecioEvento();\n\n                if (!price) {\n                    document.getElementById('quote-total').textContent = '-';\n                    return;\n                }\n\n                const total = Math.round(dias * price * 100) \/ 100;\n                document.getElementById('quote-total').textContent = moneyMXN(total);\n            }\n\n            \/\/ ==================== PAGO PAYPAL ====================\n            async function pagarEvento(submissionId) {\n                try {\n                    const { data: sessionData } = await sb.auth.getSession();\n                    if (!sessionData || !sessionData.session) {\n                        alert('Necesitas iniciar sesi\u00f3n para pagar.');\n                        return;\n                    }\n\n                    const baseUrl = `${window.location.origin}${window.location.pathname}`;\n                    const returnUrl = `${baseUrl}?tipo=evento`;\n                    const cancelUrl = `${baseUrl}?tipo=evento&cancelado=1`;\n\n                    const { data, error } = await sb.functions.invoke('create_paypal_order_evento', {\n                        body: {\n                            submission_id: Number(submissionId),\n                            return_url: returnUrl,\n                            cancel_url: cancelUrl,\n                        },\n                    });\n\n                    if (error) {\n                        const msg =\n                            error.message ||\n                            error.details ||\n                            (error.context ? JSON.stringify(error.context) : '') ||\n                            JSON.stringify(error);\n                        throw new Error(msg);\n                    }\n\n                    \/\/ Si el backend logr\u00f3 publicar usando un pago ya capturado, no reenviar a PayPal.\n                    if (data?.status === 'OK' && data?.event_updated === true) {\n                        alert('\u2705 Este evento ya ten\u00eda un pago capturado y se public\u00f3 correctamente.');\n                        await cargarMisEventos();\n                        return;\n                    }\n\n                    if (data?.status === 'CAPTURADO_SIN_APLICAR') {\n                        const detalle = data?.event_error || data?.event_result || 'No se pudo aplicar el pago capturado.';\n                        throw new Error(`Ya existe un pago capturado para este evento, pero no se pudo publicar: ${detalle}`);\n                    }\n\n                    if (!data || !data.approve_url) {\n                        throw new Error('No lleg\u00f3 approve_url. Verifica create_paypal_order_evento.');\n                    }\n\n                    window.location.href = data.approve_url;\n                } catch (e) {\n                    console.error(e);\n                    alert(e.message || e);\n                }\n            }\n\n            async function manejarRetornoPagoEvento() {\n                const { data: s } = await sb.auth.getSession();\n                if (!s || !s.session) return;\n\n                const url = new URL(window.location.href);\n                const pagoId = url.searchParams.get('pago_id');\n                const orderId = url.searchParams.get('token');\n                const tipo = url.searchParams.get('tipo');\n\n                if (!(pagoId || orderId)) return;\n                if (tipo ? (tipo !== 'evento') : false) return;\n\n                url.searchParams.delete('pago_id');\n                url.searchParams.delete('token');\n                url.searchParams.delete('tipo');\n                window.history.replaceState({}, document.title, url.toString());\n\n                const { data, error } = await sb.functions.invoke('capture_paypal_order_evento', {\n                    body: {\n                        pago_id: pagoId ? Number(pagoId) : null,\n                        paypal_order_id: orderId || null,\n                    },\n                });\n\n                if (error) {\n                    const msg = error.message || error.details || JSON.stringify(error);\n                    alert('No se pudo confirmar el pago: ' + msg);\n                    return;\n                }\n\n                if (data ? (data.status ? (data.status !== 'OK') : false) : false) {\n                    alert('No se pudo confirmar el pago: ' + data.status);\n                    return;\n                }\n\n                if (data?.event_updated === false) {\n                    alert('Pago capturado, pero no se pudo publicar el evento: ' + (data?.event_error || data?.event_result || 'sin detalle'));\n                    return;\n                }\n\n                alert('\u2705 Pago confirmado correctamente.');\n                await cargarMisEventos();\n            }\n\n            \/\/ ==================== INIT ====================\n            \/\/ listeners para cotizaci\u00f3n\n            ['publicar_desde', 'publicar_hasta', 'fecha_inicio', 'fecha_fin'].forEach(id => {\n                const el = document.getElementById(id);\n                if (!el) return;\n                el.addEventListener('change', () => actualizarCotizacionUI());\n                el.addEventListener('input', () => actualizarCotizacionUI());\n            });\n\n            window.initGooglePlaces = initGooglePlaces;\n            if (window.__gmapsInitRequested) {\n                initGooglePlaces();\n            }\n\n            checkAuth();\n        <\/script>\n    <\/div>\n<\/body>\n\n<\/html>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Mis Eventos &#8211; Saca Plan Saca Plan &#8211; Mis Eventos Iniciar Sesi\u00f3n Email Contrase\u00f1a Entrar \u00bfNo tienes cuenta? Reg\u00edstrate aqu\u00ed Crear Cuenta Email Contrase\u00f1a Crear Cuenta \u00bfYa tienes cuenta? Inicia sesi\u00f3n Mis Eventos Cerrar Sesi\u00f3n Mis Eventos Nuevo Evento \ud83d\udd0d Buscar eventos Nuevo Evento \ud83d\udcdd Informaci\u00f3n B\u00e1sica Nombre del evento * Intereses * (selecciona uno [&hellip;]<\/p>\n","protected":false},"author":5,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-308","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/saca-plan.com\/inicio\/wp-json\/wp\/v2\/pages\/308","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/saca-plan.com\/inicio\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/saca-plan.com\/inicio\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/saca-plan.com\/inicio\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/saca-plan.com\/inicio\/wp-json\/wp\/v2\/comments?post=308"}],"version-history":[{"count":46,"href":"https:\/\/saca-plan.com\/inicio\/wp-json\/wp\/v2\/pages\/308\/revisions"}],"predecessor-version":[{"id":505,"href":"https:\/\/saca-plan.com\/inicio\/wp-json\/wp\/v2\/pages\/308\/revisions\/505"}],"wp:attachment":[{"href":"https:\/\/saca-plan.com\/inicio\/wp-json\/wp\/v2\/media?parent=308"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}