commit 1c3d53432296d8738346397a22cb1e2cbe5efcc1 Author: Isaac Aracena Date: Wed Jul 29 12:17:56 2026 -0400 feat: versión inicial de GLM Hub diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..feab881 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +VITE_SUPABASE_URL="https://tu-supabase.example.com" +VITE_SUPABASE_ANON_KEY="TU_SUPABASE_ANON_KEY" +VITE_ICON_GENERATOR_WEBHOOK_URL="https://tu-n8n.example.com/webhook/glm-hub-generar-icono" +VITE_ACCESS_REQUEST_WEBHOOK_URL="https://tu-n8n.example.com/webhook/glm-hub-solicitar-acceso" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..077730e --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +.env +.env.* +!.env.example +*.log +.vite/ +.DS_Store +Thumbs.db diff --git a/README-CORRECCIONES-2026-07-28.md b/README-CORRECCIONES-2026-07-28.md new file mode 100644 index 0000000..6065a0a --- /dev/null +++ b/README-CORRECCIONES-2026-07-28.md @@ -0,0 +1,26 @@ +# GLM Hub — correcciones del 28 de julio de 2026 + +## Aplicación + +- La renovación automática del token de Supabase ya no reinicia la vista, no regresa al Hub y no vuelve a cargar el catálogo cuando el usuario cambia de pestaña y regresa. +- El formulario de Administración conserva el texto, selección, scroll y pantalla activa durante los eventos `SIGNED_IN` repetidos y `TOKEN_REFRESHED` del mismo usuario. +- La solicitud de generación usa un POST `text/plain` con el JWT dentro del cuerpo para evitar que un preflight CORS bloquee la llamada antes de llegar a n8n. +- El frontend acepta directamente la imagen binaria devuelta por n8n y conserva compatibilidad con la respuesta JSON del workflow anterior. +- Se añadió timeout de 3 minutos y mensajes de error más claros. + +## Workflow n8n + +Archivo: `n8n/GLM-Hub-Generar-Icono-Gemini-Nodo-Nativo.json` + +- Usa el nodo nativo **Google Gemini → Image → Generate an Image**. +- Modelo configurado: `models/gemini-3-pro-image`. +- La imagen se devuelve directamente como binario por el webhook. +- El webhook acepta el JWT en el cuerpo o en el encabezado Authorization. +- Continúa validando en Supabase que el usuario sea administrador. +- CORS está permitido para que funcione tanto en producción como durante pruebas locales. + +## Único ajuste manual después de importar + +Abre el nodo **Generate an image** y confirma la credencial existente **Isaac - Gemini Api Pago**. El ID interno de una credencial no puede incluirse de forma portable en un JSON exportado. + +Antes de activar este workflow, desactiva el workflow anterior que utiliza la misma ruta `glm-hub-generar-icono`. diff --git a/README-IMPLEMENTACION-COMPLETA.md b/README-IMPLEMENTACION-COMPLETA.md new file mode 100644 index 0000000..a544538 --- /dev/null +++ b/README-IMPLEMENTACION-COMPLETA.md @@ -0,0 +1,141 @@ +# GLM Hub — Implementación compartida con Supabase e íconos con Gemini + +Este proyecto queda preparado para que el catálogo sea compartido por todo el equipo. Las aplicaciones ya no se guardan como catálogo operativo en el navegador: se almacenan en Supabase y se actualizan en las sesiones abiertas mediante Realtime. + +## Archivos principales + +- `SUPABASE-GLM-HUB-COMPLETO.sql`: crea o actualiza todo lo necesario en Supabase. +- `n8n/GLM-Hub-Generar-Icono-Gemini.json`: workflow importable de n8n. +- `.env`: variables de la app web. + +## Orden correcto de implementación + +### 1. Ejecutar el SQL completo en Supabase + +En Supabase Studio abre **SQL Editor**, pega el contenido completo de: + +`SUPABASE-GLM-HUB-COMPLETO.sql` + +Luego ejecútalo una sola vez. El script es reutilizable: usa `if not exists`, reemplaza funciones y recrea políticas de forma controlada. + +El script configura: + +- Lista de usuarios autorizados y roles. +- Los cinco administradores definidos para GLM Hub. +- Catálogo compartido `glm_hub_apps`. +- Favoritos personales `glm_hub_favorites`. +- Accesos recientes personales `glm_hub_recent_apps`. +- Bucket público `glm-hub-icons` con máximo de 1 MB. +- Políticas RLS: todos los usuarios activos ven el catálogo publicado; solo administradores pueden crear, editar y eliminar. +- Publicación de cambios de aplicaciones en Supabase Realtime. +- Función segura `glm_hub_icon_generation_access()` para validar desde n8n que quien solicita el ícono sea administrador. + +### 2. Importar y configurar el workflow de n8n + +Importa: + +`n8n/GLM-Hub-Generar-Icono-Gemini.json` + +Configura estas variables de entorno en la instancia de n8n: + +```env +SUPABASE_URL=https://dbit.digitalcompass.agency +SUPABASE_ANON_KEY=TU_CLAVE_ANON_DE_SUPABASE +GEMINI_API_KEY=TU_API_KEY_DE_GEMINI +``` + +Reinicia n8n después de añadirlas, abre el workflow y actívalo. + +Webhook de producción esperado: + +```text +https://agenteit.digitalcompass.agency/webhook/glm-hub-generar-icono +``` + +El workflow: + +1. Recibe nombre y descripción desde GLM Hub. +2. Verifica el JWT activo de Supabase. +3. Confirma en la base de datos que el usuario sea administrador. +4. Genera un ícono cuadrado sin texto con Gemini. +5. Devuelve la imagen al formulario como PNG. + +La clave de Gemini debe permanecer únicamente en n8n. No debe añadirse al `.env` de Vite ni enviarse al navegador. + +### 3. Revisar las variables del frontend + +El `.env` incluido contiene: + +```env +VITE_SUPABASE_URL="https://dbit.digitalcompass.agency" +VITE_SUPABASE_ANON_KEY="..." +VITE_ICON_GENERATOR_WEBHOOK_URL="https://agenteit.digitalcompass.agency/webhook/glm-hub-generar-icono" +``` + +Cambia la URL del webhook solamente si el workflow se publica en otro dominio o ruta. + +### 4. Instalar, validar y compilar + +Desde la carpeta raíz del proyecto: + +```bash +npm install +npm run typecheck +npm run build +``` + +La compilación queda en `dist/`. + +### 5. Migrar las aplicaciones que ya estaban creadas + +Después de ejecutar el SQL y desplegar esta versión: + +1. Inicia sesión como administrador desde el mismo navegador donde estaban guardadas las aplicaciones anteriores. +2. La app compara el catálogo local con `glm_hub_apps` y migra solamente las aplicaciones cuyo nombre todavía no exista en Supabase. +3. Los íconos se suben al bucket `glm-hub-icons`. +4. Desde ese momento, cualquier usuario autorizado verá el mismo catálogo publicado al iniciar sesión. + +La comparación por nombre evita duplicados y permite reanudar la migración en otro inicio de sesión si una carga anterior quedó incompleta. Cuando termina correctamente, el navegador registra la migración como completada para que una aplicación eliminada después desde Supabase no vuelva a crearse desde el catálogo local antiguo. + +## Funcionamiento del Hub + +- **Acceso rápido:** incluye todas las aplicaciones marcadas como favoritas por el usuario. Si todavía tiene menos de tres favoritas, se completa temporalmente con recientes o aplicaciones disponibles. Se muestran seis por página y la paginación aparece solo cuando hace falta. +- **Todas las aplicaciones:** muestra diez por página en Todas, Administración, Recursos Humanos y CDC. En cada filtro, los favoritos se ordenan antes que el resto y después se aplica la paginación. +- **Administración:** también pagina el catálogo cada diez registros. +- **Cambios compartidos:** al crear, editar o eliminar una aplicación, el cambio se guarda en Supabase y llega al resto de sesiones abiertas. +- **Favoritos y recientes:** son personales para cada usuario; no afectan la vista de los demás. + +## Generación del ícono + +El botón **Generar ícono con IA** se habilita solamente cuando se completan: + +- Nombre de la aplicación. +- Descripción breve. + +Mientras Gemini está trabajando: + +- Se muestra una pantalla de carga bloqueante. +- No se puede publicar, cancelar, editar ni salir accidentalmente de la página. +- Al finalizar, el ícono aparece en la vista previa y puede publicarse junto con la aplicación. + +## Dar o quitar acceso + +Desde `public.glm_hub_authorized_users`: + +```sql +-- Dar acceso normal +insert into public.glm_hub_authorized_users (email, full_name, role, is_active) +values ('persona@gomezleemarketing.com', 'Nombre de la persona', 'member', true); + +-- Quitar acceso sin borrar +update public.glm_hub_authorized_users +set is_active = false +where email = 'persona@gomezleemarketing.com'; + +-- Reactivar +update public.glm_hub_authorized_users +set is_active = true +where email = 'persona@gomezleemarketing.com'; +``` + +Los administradores están restringidos por una validación de base de datos a los cinco correos definidos en el script. diff --git a/README-SOLICITUDES-ACCESO.md b/README-SOLICITUDES-ACCESO.md new file mode 100644 index 0000000..51e6285 --- /dev/null +++ b/README-SOLICITUDES-ACCESO.md @@ -0,0 +1,75 @@ +# GLM Hub — Solicitudes de acceso + +## Cambios incluidos + +- Se retiró el texto de ayuda **“Directa y fácil de escanear.”** debajo de Descripción breve. +- **Control del catálogo** sigue renderizándose exclusivamente para usuarios con `role = admin`. +- Los usuarios normales ven una nueva sección **¿No tienes acceso a una aplicación?**. +- El formulario de solicitud se alimenta directamente del catálogo publicado en Supabase, por lo que al crear, ocultar o eliminar aplicaciones el dropdown se actualiza sin editar código. +- La pantalla queda bloqueada mientras n8n registra la solicitud y envía los correos. +- Los correos de aprobación se envían individualmente a Isaac Aracena, José Leopoldo Gómez y Máximo Gómez. +- La primera decisión confirmada queda registrada de forma atómica en Supabase. Los clics posteriores no pueden cambiarla ni volver a avisar a IT Support. +- Tras la decisión se envía un correo HTML a `itsupport@gomezleemarketing.com` con solicitante, aplicación, decisión y persona que decidió. + +## 1. Supabase + +Ejecuta completo: + +`SUPABASE-GLM-HUB-COMPLETO.sql` + +El script conserva las tablas anteriores y agrega: + +- `glm_hub_access_requests` +- `glm_hub_access_request_approvers` +- `glm_hub_prepare_access_request(uuid)` +- `glm_hub_issue_access_request_tokens(uuid)` +- `glm_hub_decide_access_request(text, text)` + +La función que entrega los enlaces de aprobación solo puede ejecutarse con `service_role`; los enlaces nunca se devuelven al navegador del solicitante. + +## 2. n8n + +Importa: + +`n8n/GLM-Hub-Solicitudes-Acceso-Aprobacion.json` + +Después configura: + +1. En **Crear solicitud en Supabase** reemplaza `TU_SUPABASE_ANON_KEY` en `apikey`. +2. En **Emitir enlaces de aprobación** reemplaza `TU_SUPABASE_SERVICE_ROLE_KEY` en `apikey` y `Authorization`. +3. En **Registrar decisión en Supabase** reemplaza `TU_SUPABASE_ANON_KEY` en `apikey` y dentro de `Bearer TU_SUPABASE_ANON_KEY`. +4. Selecciona una credencial Gmail válida en: + - **Enviar solicitud a aprobadores** + - **Enviar decisión a IT Support** +5. Guarda, publica y activa el workflow. + +El workflow no usa variables de entorno de n8n. + +## 3. Frontend + +El `.env` ya incluye: + +```env +VITE_ACCESS_REQUEST_WEBHOOK_URL="https://agenteit.digitalcompass.agency/webhook/glm-hub-solicitar-acceso" +``` + +El ZIP también contiene un `dist` actualizado. La versión fuente puede recompilarse normalmente con: + +```bash +npm install +npm run build +``` + +## Decisión desde correo + +Los botones del correo abren una pantalla de confirmación antes de registrar la decisión. Esto evita que un escáner automático de enlaces de Gmail o seguridad corporativa apruebe o rechace una solicitud sin intervención humana. + +Los botones permanecen visibles en los correos ya entregados, pero después de la primera decisión Supabase bloquea cualquier cambio posterior y muestra que la solicitud ya fue atendida. + + +## Corrección 2026-07-29 + +- El nodo **Crear solicitud en Supabase** usa la clave `anon` en `apikey` y el JWT vigente del usuario en `Authorization`. +- Los nodos internos para emitir enlaces y registrar decisiones conservan la clave `service_role`. +- Los errores visibles en el portal son mensajes para usuarios; los detalles técnicos quedan solamente en la ejecución de n8n. +- El selector de áreas incluye **CDC**. diff --git a/README-SUPABASE.md b/README-SUPABASE.md new file mode 100644 index 0000000..247ef74 --- /dev/null +++ b/README-SUPABASE.md @@ -0,0 +1,11 @@ +# GLM Hub — Supabase compartido + +La guía vigente está en: + +`README-IMPLEMENTACION-COMPLETA.md` + +Ejecuta únicamente el script completo: + +`SUPABASE-GLM-HUB-COMPLETO.sql` + +El catálogo, los íconos, favoritos, recientes, RLS y Realtime están incluidos en ese único archivo. diff --git a/SUPABASE-GLM-HUB-COMPLETO.sql b/SUPABASE-GLM-HUB-COMPLETO.sql new file mode 100644 index 0000000..f1edc6e --- /dev/null +++ b/SUPABASE-GLM-HUB-COMPLETO.sql @@ -0,0 +1,894 @@ +-- ============================================================================ +-- GLM HUB — ESQUEMA COMPLETO DE SUPABASE +-- ============================================================================ +-- Incluye: +-- 1) Usuarios autorizados y roles. +-- 2) Catálogo compartido para todos los usuarios del Hub. +-- 3) Favoritos y accesos recientes por usuario. +-- 4) Bucket público para los íconos. +-- 5) RLS, permisos y Realtime. +-- 6) Función segura que usa n8n para validar al administrador. +-- 7) Solicitudes de acceso, aprobadores y decisión única. +-- +-- Ejecutar el archivo completo una sola vez en: +-- Supabase Studio > SQL Editor > New query > Run. +-- El script es idempotente y puede volver a ejecutarse para restaurar políticas. +-- ============================================================================ + +begin; + +create extension if not exists pgcrypto with schema extensions; + +-- -------------------------------------------------------------------------- +-- 1. USUARIOS AUTORIZADOS +-- -------------------------------------------------------------------------- + +create table if not exists public.glm_hub_authorized_users ( + id uuid primary key default gen_random_uuid(), + email text not null unique, + full_name text, + role text not null default 'member', + is_active boolean not null default true, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now(), + + constraint glm_hub_authorized_users_role_check + check (role in ('admin', 'member')), + + constraint glm_hub_authorized_users_email_normalized_check + check (email = lower(btrim(email))), + + constraint glm_hub_authorized_users_domain_check + check (email ~ '^[a-z0-9.!#$%&''*+/=?^_`{|}~-]+@gomezleemarketing\.com$'), + + constraint glm_hub_authorized_users_admin_check + check ( + role <> 'admin' + or email in ( + 'jgomez@gomezleemarketing.com', + 'iaracena@gomezleemarketing.com', + 'ethen@gomezleemarketing.com', + 'mgomez@gomezleemarketing.com', + 'lmatos@gomezleemarketing.com' + ) + ) +); + +create or replace function public.glm_hub_normalize_authorized_user() +returns trigger +language plpgsql +set search_path = public +as $$ +begin + new.email := lower(btrim(new.email)); + new.full_name := nullif(btrim(new.full_name), ''); + new.updated_at := now(); + return new; +end; +$$; + +drop trigger if exists glm_hub_normalize_authorized_user_trigger + on public.glm_hub_authorized_users; + +create trigger glm_hub_normalize_authorized_user_trigger +before insert or update on public.glm_hub_authorized_users +for each row +execute function public.glm_hub_normalize_authorized_user(); + +alter table public.glm_hub_authorized_users enable row level security; +alter table public.glm_hub_authorized_users force row level security; + +revoke all on table public.glm_hub_authorized_users from anon; +revoke all on table public.glm_hub_authorized_users from authenticated; +grant select on table public.glm_hub_authorized_users to authenticated; +grant all on table public.glm_hub_authorized_users to service_role; + +drop policy if exists "GLM Hub users can read only their active access" + on public.glm_hub_authorized_users; + +create policy "GLM Hub users can read only their active access" +on public.glm_hub_authorized_users +for select +to authenticated +using ( + is_active = true + and email = lower(coalesce((select auth.jwt() ->> 'email'), '')) +); + +insert into public.glm_hub_authorized_users (email, full_name, role, is_active) +values + ('jgomez@gomezleemarketing.com', 'José Leopoldo Gómez', 'admin', true), + ('iaracena@gomezleemarketing.com', 'Isaac Aracena', 'admin', true), + ('ethen@gomezleemarketing.com', 'Eidan Then', 'admin', true), + ('mgomez@gomezleemarketing.com', 'Máximo Gomez', 'admin', true), + ('lmatos@gomezleemarketing.com', 'Luis Matos', 'admin', true) +on conflict (email) do update +set + full_name = excluded.full_name, + role = excluded.role, + is_active = true, + updated_at = now(); + +-- Estas funciones se ejecutan con los permisos/RLS del usuario autenticado. +create or replace function public.glm_hub_is_active_user() +returns boolean +language sql +stable +security invoker +set search_path = public, auth +as $$ + select exists ( + select 1 + from public.glm_hub_authorized_users as access + where access.is_active = true + and access.email = lower(coalesce((select auth.jwt() ->> 'email'), '')) + ); +$$; + +create or replace function public.glm_hub_is_admin() +returns boolean +language sql +stable +security invoker +set search_path = public, auth +as $$ + select exists ( + select 1 + from public.glm_hub_authorized_users as access + where access.is_active = true + and access.role = 'admin' + and access.email = lower(coalesce((select auth.jwt() ->> 'email'), '')) + ); +$$; + +-- n8n llama esta función usando el JWT recibido desde el frontend. +-- SECURITY DEFINER permite consultar la tabla interna sin quedar bloqueado por RLS, +-- pero el correo y el usuario siempre se toman del JWT real de Supabase. +create or replace function public.glm_hub_icon_generation_access() +returns jsonb +language plpgsql +stable +security definer +set search_path = '' +as $$ +declare + v_email text; + v_user_id text; + v_authorized boolean; +begin + v_email := lower(coalesce(auth.jwt() ->> 'email', '')); + v_user_id := coalesce(auth.uid()::text, ''); + + select exists ( + select 1 + from public.glm_hub_authorized_users as access + where lower(access.email) = v_email + and access.role = 'admin' + and access.is_active = true + ) + into v_authorized; + + return jsonb_build_object( + 'authorized', coalesce(v_authorized, false), + 'email', v_email, + 'userId', v_user_id + ); +end; +$$; + +alter function public.glm_hub_icon_generation_access() owner to postgres; + +revoke all on function public.glm_hub_is_active_user() from public, anon; +revoke all on function public.glm_hub_is_admin() from public, anon; +revoke all on function public.glm_hub_icon_generation_access() from public, anon; +grant execute on function public.glm_hub_is_active_user() to authenticated; +grant execute on function public.glm_hub_is_admin() to authenticated; +grant execute on function public.glm_hub_icon_generation_access() to authenticated; +grant execute on function public.glm_hub_is_active_user() to service_role; +grant execute on function public.glm_hub_is_admin() to service_role; +grant execute on function public.glm_hub_icon_generation_access() to service_role; + +-- -------------------------------------------------------------------------- +-- 2. CATÁLOGO COMPARTIDO +-- -------------------------------------------------------------------------- + +create table if not exists public.glm_hub_apps ( + id uuid primary key default gen_random_uuid(), + name text not null, + description text not null, + category text not null, + url text, + icon_url text not null, + visibility text not null default 'published', + created_by uuid default auth.uid() references auth.users(id) on delete set null, + updated_by uuid default auth.uid() references auth.users(id) on delete set null, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now(), + + constraint glm_hub_apps_name_length_check + check (char_length(btrim(name)) between 2 and 60), + constraint glm_hub_apps_description_length_check + check (char_length(btrim(description)) between 1 and 180), + constraint glm_hub_apps_category_check + check (category in ('Administración', 'Recursos Humanos', 'CDC')), + constraint glm_hub_apps_visibility_check + check (visibility in ('published', 'hidden')), + constraint glm_hub_apps_icon_url_check + check (icon_url ~ '^https://') +); + +create unique index if not exists glm_hub_apps_name_unique_ci + on public.glm_hub_apps (lower(btrim(name))); + +create index if not exists glm_hub_apps_visibility_category_name_idx + on public.glm_hub_apps (visibility, category, name); + +create or replace function public.glm_hub_normalize_app() +returns trigger +language plpgsql +set search_path = public +as $$ +begin + new.name := btrim(new.name); + new.description := btrim(new.description); + new.category := btrim(new.category); + new.url := nullif(btrim(new.url), ''); + new.icon_url := btrim(new.icon_url); + new.updated_at := now(); + return new; +end; +$$; + +drop trigger if exists glm_hub_normalize_app_trigger on public.glm_hub_apps; +create trigger glm_hub_normalize_app_trigger +before insert or update on public.glm_hub_apps +for each row +execute function public.glm_hub_normalize_app(); + +alter table public.glm_hub_apps enable row level security; +alter table public.glm_hub_apps force row level security; +alter table public.glm_hub_apps replica identity full; + +revoke all on table public.glm_hub_apps from anon; +revoke all on table public.glm_hub_apps from authenticated; +grant select, insert, update, delete on table public.glm_hub_apps to authenticated; +grant all on table public.glm_hub_apps to service_role; + +drop policy if exists "GLM Hub active users read shared apps" on public.glm_hub_apps; +drop policy if exists "GLM Hub admins create apps" on public.glm_hub_apps; +drop policy if exists "GLM Hub admins update apps" on public.glm_hub_apps; +drop policy if exists "GLM Hub admins delete apps" on public.glm_hub_apps; + +create policy "GLM Hub active users read shared apps" +on public.glm_hub_apps +for select +to authenticated +using ( + public.glm_hub_is_active_user() + and (visibility = 'published' or public.glm_hub_is_admin()) +); + +create policy "GLM Hub admins create apps" +on public.glm_hub_apps +for insert +to authenticated +with check ( + public.glm_hub_is_admin() + and created_by = (select auth.uid()) + and updated_by = (select auth.uid()) +); + +create policy "GLM Hub admins update apps" +on public.glm_hub_apps +for update +to authenticated +using (public.glm_hub_is_admin()) +with check ( + public.glm_hub_is_admin() + and updated_by = (select auth.uid()) +); + +create policy "GLM Hub admins delete apps" +on public.glm_hub_apps +for delete +to authenticated +using (public.glm_hub_is_admin()); + +-- -------------------------------------------------------------------------- +-- 3. FAVORITOS POR USUARIO +-- -------------------------------------------------------------------------- + +create table if not exists public.glm_hub_favorites ( + user_id uuid not null references auth.users(id) on delete cascade, + app_id uuid not null references public.glm_hub_apps(id) on delete cascade, + created_at timestamptz not null default now(), + primary key (user_id, app_id) +); + +create index if not exists glm_hub_favorites_user_created_idx + on public.glm_hub_favorites (user_id, created_at desc); + +alter table public.glm_hub_favorites enable row level security; +alter table public.glm_hub_favorites force row level security; + +revoke all on table public.glm_hub_favorites from anon; +revoke all on table public.glm_hub_favorites from authenticated; +grant select, insert, update, delete on table public.glm_hub_favorites to authenticated; +grant all on table public.glm_hub_favorites to service_role; + +drop policy if exists "GLM Hub users read their favorites" on public.glm_hub_favorites; +drop policy if exists "GLM Hub users create their favorites" on public.glm_hub_favorites; +drop policy if exists "GLM Hub users update their favorites" on public.glm_hub_favorites; +drop policy if exists "GLM Hub users delete their favorites" on public.glm_hub_favorites; + +create policy "GLM Hub users read their favorites" +on public.glm_hub_favorites +for select +to authenticated +using (public.glm_hub_is_active_user() and user_id = (select auth.uid())); + +create policy "GLM Hub users create their favorites" +on public.glm_hub_favorites +for insert +to authenticated +with check (public.glm_hub_is_active_user() and user_id = (select auth.uid())); + +create policy "GLM Hub users update their favorites" +on public.glm_hub_favorites +for update +to authenticated +using (public.glm_hub_is_active_user() and user_id = (select auth.uid())) +with check (public.glm_hub_is_active_user() and user_id = (select auth.uid())); + +create policy "GLM Hub users delete their favorites" +on public.glm_hub_favorites +for delete +to authenticated +using (public.glm_hub_is_active_user() and user_id = (select auth.uid())); + +-- -------------------------------------------------------------------------- +-- 4. ACCESOS RECIENTES POR USUARIO +-- -------------------------------------------------------------------------- + +create table if not exists public.glm_hub_recent_apps ( + user_id uuid not null references auth.users(id) on delete cascade, + app_id uuid not null references public.glm_hub_apps(id) on delete cascade, + last_opened_at timestamptz not null default now(), + primary key (user_id, app_id) +); + +create index if not exists glm_hub_recent_apps_user_date_idx + on public.glm_hub_recent_apps (user_id, last_opened_at desc); + +alter table public.glm_hub_recent_apps enable row level security; +alter table public.glm_hub_recent_apps force row level security; + +revoke all on table public.glm_hub_recent_apps from anon; +revoke all on table public.glm_hub_recent_apps from authenticated; +grant select, insert, update, delete on table public.glm_hub_recent_apps to authenticated; +grant all on table public.glm_hub_recent_apps to service_role; + +drop policy if exists "GLM Hub users read their recent apps" on public.glm_hub_recent_apps; +drop policy if exists "GLM Hub users create their recent apps" on public.glm_hub_recent_apps; +drop policy if exists "GLM Hub users update their recent apps" on public.glm_hub_recent_apps; +drop policy if exists "GLM Hub users delete their recent apps" on public.glm_hub_recent_apps; + +create policy "GLM Hub users read their recent apps" +on public.glm_hub_recent_apps +for select +to authenticated +using (public.glm_hub_is_active_user() and user_id = (select auth.uid())); + +create policy "GLM Hub users create their recent apps" +on public.glm_hub_recent_apps +for insert +to authenticated +with check (public.glm_hub_is_active_user() and user_id = (select auth.uid())); + +create policy "GLM Hub users update their recent apps" +on public.glm_hub_recent_apps +for update +to authenticated +using (public.glm_hub_is_active_user() and user_id = (select auth.uid())) +with check (public.glm_hub_is_active_user() and user_id = (select auth.uid())); + +create policy "GLM Hub users delete their recent apps" +on public.glm_hub_recent_apps +for delete +to authenticated +using (public.glm_hub_is_active_user() and user_id = (select auth.uid())); + +-- -------------------------------------------------------------------------- +-- 5. STORAGE PARA LOS ÍCONOS +-- -------------------------------------------------------------------------- + +insert into storage.buckets ( + id, + name, + public, + file_size_limit, + allowed_mime_types +) +values ( + 'glm-hub-icons', + 'glm-hub-icons', + true, + 1048576, + array['image/png', 'image/jpeg', 'image/webp']::text[] +) +on conflict (id) do update +set + name = excluded.name, + public = true, + file_size_limit = excluded.file_size_limit, + allowed_mime_types = excluded.allowed_mime_types; + +drop policy if exists "GLM Hub active users read icons" on storage.objects; +drop policy if exists "GLM Hub admins upload icons" on storage.objects; +drop policy if exists "GLM Hub admins update icons" on storage.objects; +drop policy if exists "GLM Hub admins delete icons" on storage.objects; + +create policy "GLM Hub active users read icons" +on storage.objects +for select +to authenticated +using ( + bucket_id = 'glm-hub-icons' + and public.glm_hub_is_active_user() +); + +create policy "GLM Hub admins upload icons" +on storage.objects +for insert +to authenticated +with check ( + bucket_id = 'glm-hub-icons' + and public.glm_hub_is_admin() +); + +create policy "GLM Hub admins update icons" +on storage.objects +for update +to authenticated +using ( + bucket_id = 'glm-hub-icons' + and public.glm_hub_is_admin() +) +with check ( + bucket_id = 'glm-hub-icons' + and public.glm_hub_is_admin() +); + +create policy "GLM Hub admins delete icons" +on storage.objects +for delete +to authenticated +using ( + bucket_id = 'glm-hub-icons' + and public.glm_hub_is_admin() +); + +-- -------------------------------------------------------------------------- +-- 6. SOLICITUDES DE ACCESO A APLICACIONES +-- -------------------------------------------------------------------------- + +create table if not exists public.glm_hub_access_requests ( + id uuid primary key default gen_random_uuid(), + requester_user_id uuid not null references auth.users(id) on delete cascade, + requester_name text not null, + requester_email text not null, + app_id uuid references public.glm_hub_apps(id) on delete set null, + app_name text not null, + app_category text not null, + status text not null default 'pending', + decided_by_name text, + decided_by_email text, + decided_at timestamptz, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now(), + + constraint glm_hub_access_requests_status_check + check (status in ('pending', 'approved', 'rejected')), + constraint glm_hub_access_requests_requester_email_check + check (requester_email = lower(btrim(requester_email))), + constraint glm_hub_access_requests_category_check + check (app_category in ('Administración', 'Recursos Humanos', 'CDC')) +); + +create unique index if not exists glm_hub_access_requests_one_pending_idx + on public.glm_hub_access_requests (requester_email, app_id) + where status = 'pending' and app_id is not null; + +create index if not exists glm_hub_access_requests_status_created_idx + on public.glm_hub_access_requests (status, created_at desc); + +create table if not exists public.glm_hub_access_request_approvers ( + id uuid primary key default gen_random_uuid(), + request_id uuid not null references public.glm_hub_access_requests(id) on delete cascade, + approver_name text not null, + approver_email text not null, + decision_token text not null unique, + decision_token_hash text not null unique, + clicked_at timestamptz, + created_at timestamptz not null default now(), + + constraint glm_hub_access_request_approvers_email_check + check ( + approver_email in ( + 'iaracena@gomezleemarketing.com', + 'jgomez@gomezleemarketing.com', + 'mgomez@gomezleemarketing.com' + ) + ), + constraint glm_hub_access_request_approvers_unique_request_email + unique (request_id, approver_email) +); + +alter table public.glm_hub_access_requests enable row level security; +alter table public.glm_hub_access_requests force row level security; +alter table public.glm_hub_access_request_approvers enable row level security; +alter table public.glm_hub_access_request_approvers force row level security; + +revoke all on table public.glm_hub_access_requests from anon, authenticated; +revoke all on table public.glm_hub_access_request_approvers from anon, authenticated; +grant all on table public.glm_hub_access_requests to service_role; +grant all on table public.glm_hub_access_request_approvers to service_role; + +-- Los usuarios pueden consultar solamente sus propias solicitudes desde Supabase, +-- aunque el frontend actual no necesita leer esta tabla. +drop policy if exists "GLM Hub users read their access requests" + on public.glm_hub_access_requests; + +create policy "GLM Hub users read their access requests" +on public.glm_hub_access_requests +for select +to authenticated +using ( + requester_user_id = auth.uid() + and public.glm_hub_is_active_user() +); + +grant select on table public.glm_hub_access_requests to authenticated; + +-- Registra la solicitud usando exclusivamente la identidad del JWT del usuario. +-- No devuelve enlaces de aprobación al navegador, evitando que el solicitante +-- pueda obtenerlos o aprobarse a sí mismo. +create or replace function public.glm_hub_prepare_access_request(p_app_id uuid) +returns jsonb +language plpgsql +volatile +security definer +set search_path = '' +as $$ +declare + v_user_id uuid; + v_email text; + v_name text; + v_role text; + v_app_name text; + v_app_category text; + v_request_id uuid; +begin + v_user_id := auth.uid(); + v_email := lower(coalesce(auth.jwt() ->> 'email', '')); + + if v_user_id is null or v_email = '' then + return jsonb_build_object('ok', false, 'error', 'Tu sesión no es válida. Cierra sesión e inicia nuevamente.'); + end if; + + select coalesce(nullif(btrim(access.full_name), ''), split_part(v_email, '@', 1)), access.role + into v_name, v_role + from public.glm_hub_authorized_users as access + where access.email = v_email + and access.is_active = true + limit 1; + + if not found then + return jsonb_build_object('ok', false, 'error', 'El usuario no está autorizado en GLM Hub.'); + end if; + + if v_role = 'admin' then + return jsonb_build_object('ok', false, 'error', 'Los administradores no necesitan solicitar acceso desde el Hub.'); + end if; + + select app.name, app.category + into v_app_name, v_app_category + from public.glm_hub_apps as app + where app.id = p_app_id + and app.visibility = 'published' + limit 1; + + if not found then + return jsonb_build_object('ok', false, 'error', 'La aplicación seleccionada ya no está publicada.'); + end if; + + if exists ( + select 1 + from public.glm_hub_access_requests as request + where request.requester_email = v_email + and request.app_id = p_app_id + and request.status = 'pending' + ) then + return jsonb_build_object( + 'ok', false, + 'code', 'PENDING_EXISTS', + 'error', 'Ya existe una solicitud pendiente para esta aplicación.' + ); + end if; + + insert into public.glm_hub_access_requests ( + requester_user_id, + requester_name, + requester_email, + app_id, + app_name, + app_category + ) + values ( + v_user_id, + v_name, + v_email, + p_app_id, + v_app_name, + v_app_category + ) + returning id into v_request_id; + + return jsonb_build_object( + 'ok', true, + 'requestId', v_request_id, + 'requesterName', v_name, + 'requesterEmail', v_email, + 'appName', v_app_name, + 'appCategory', v_app_category + ); +end; +$$; + +alter function public.glm_hub_prepare_access_request(uuid) owner to postgres; +revoke all on function public.glm_hub_prepare_access_request(uuid) from public, anon; +grant execute on function public.glm_hub_prepare_access_request(uuid) to authenticated; +grant execute on function public.glm_hub_prepare_access_request(uuid) to service_role; + +-- Emite o recupera los tres enlaces de decisión. Solo n8n, usando la clave +-- service_role guardada en su nodo HTTP Request, puede ejecutar esta función. +create or replace function public.glm_hub_issue_access_request_tokens(p_request_id uuid) +returns jsonb +language plpgsql +volatile +security definer +set search_path = '' +as $$ +declare + v_request public.glm_hub_access_requests%rowtype; + v_token_isaac text; + v_token_jose text; + v_token_maximo text; +begin + select request.* + into v_request + from public.glm_hub_access_requests as request + where request.id = p_request_id + and request.status = 'pending' + for update; + + if not found then + return jsonb_build_object('ok', false, 'error', 'La solicitud no existe o ya fue atendida.'); + end if; + + if not exists ( + select 1 + from public.glm_hub_access_request_approvers as approver + where approver.request_id = p_request_id + ) then + v_token_isaac := encode(extensions.gen_random_bytes(32), 'hex'); + v_token_jose := encode(extensions.gen_random_bytes(32), 'hex'); + v_token_maximo := encode(extensions.gen_random_bytes(32), 'hex'); + + insert into public.glm_hub_access_request_approvers ( + request_id, + approver_name, + approver_email, + decision_token, + decision_token_hash + ) + values + ( + p_request_id, + 'Isaac Aracena', + 'iaracena@gomezleemarketing.com', + v_token_isaac, + encode(extensions.digest(v_token_isaac, 'sha256'), 'hex') + ), + ( + p_request_id, + 'José Leopoldo Gómez', + 'jgomez@gomezleemarketing.com', + v_token_jose, + encode(extensions.digest(v_token_jose, 'sha256'), 'hex') + ), + ( + p_request_id, + 'Máximo Gómez', + 'mgomez@gomezleemarketing.com', + v_token_maximo, + encode(extensions.digest(v_token_maximo, 'sha256'), 'hex') + ); + end if; + + return jsonb_build_object( + 'ok', true, + 'requestId', v_request.id, + 'requesterName', v_request.requester_name, + 'requesterEmail', v_request.requester_email, + 'appName', v_request.app_name, + 'appCategory', v_request.app_category, + 'approvers', ( + select jsonb_agg( + jsonb_build_object( + 'name', approver.approver_name, + 'email', approver.approver_email, + 'token', approver.decision_token + ) + order by approver.approver_email + ) + from public.glm_hub_access_request_approvers as approver + where approver.request_id = p_request_id + ) + ); +end; +$$; + +alter function public.glm_hub_issue_access_request_tokens(uuid) owner to postgres; +revoke all on function public.glm_hub_issue_access_request_tokens(uuid) from public, anon, authenticated; +grant execute on function public.glm_hub_issue_access_request_tokens(uuid) to service_role; + +-- Registra una sola decisión de forma atómica. Aunque los botones sigan visibles +-- en correos ya entregados, cualquier segundo clic queda bloqueado en la base de datos. +create or replace function public.glm_hub_decide_access_request( + p_token text, + p_decision text +) +returns jsonb +language plpgsql +volatile +security definer +set search_path = '' +as $$ +declare + v_request_id uuid; + v_approver_name text; + v_approver_email text; + v_request public.glm_hub_access_requests%rowtype; + v_normalized_decision text; +begin + v_normalized_decision := lower(btrim(coalesce(p_decision, ''))); + + if v_normalized_decision not in ('approved', 'rejected') then + return jsonb_build_object('ok', false, 'error', 'La decisión recibida no es válida.'); + end if; + + select approver.request_id, approver.approver_name, approver.approver_email + into v_request_id, v_approver_name, v_approver_email + from public.glm_hub_access_request_approvers as approver + where approver.decision_token_hash = encode( + extensions.digest(btrim(coalesce(p_token, '')), 'sha256'), + 'hex' + ) + limit 1; + + if not found then + return jsonb_build_object('ok', false, 'error', 'El enlace de decisión no es válido o ya no existe.'); + end if; + + update public.glm_hub_access_requests as request + set + status = v_normalized_decision, + decided_by_name = v_approver_name, + decided_by_email = v_approver_email, + decided_at = now(), + updated_at = now() + where request.id = v_request_id + and request.status = 'pending' + returning request.* into v_request; + + if found then + update public.glm_hub_access_request_approvers + set clicked_at = now() + where request_id = v_request_id + and approver_email = v_approver_email; + + return jsonb_build_object( + 'ok', true, + 'alreadyDecided', false, + 'requestId', v_request.id, + 'status', v_request.status, + 'requesterName', v_request.requester_name, + 'requesterEmail', v_request.requester_email, + 'appName', v_request.app_name, + 'appCategory', v_request.app_category, + 'decidedByName', v_request.decided_by_name, + 'decidedByEmail', v_request.decided_by_email, + 'decidedAt', v_request.decided_at + ); + end if; + + select request.* + into v_request + from public.glm_hub_access_requests as request + where request.id = v_request_id; + + return jsonb_build_object( + 'ok', false, + 'alreadyDecided', true, + 'status', v_request.status, + 'requesterName', v_request.requester_name, + 'requesterEmail', v_request.requester_email, + 'appName', v_request.app_name, + 'decidedByName', coalesce(v_request.decided_by_name, ''), + 'decidedByEmail', coalesce(v_request.decided_by_email, ''), + 'decidedAt', v_request.decided_at, + 'error', 'Esta solicitud ya fue atendida y no admite otra decisión.' + ); +end; +$$; + +alter function public.glm_hub_decide_access_request(text, text) owner to postgres; +revoke all on function public.glm_hub_decide_access_request(text, text) from public; +grant execute on function public.glm_hub_decide_access_request(text, text) to anon; +grant execute on function public.glm_hub_decide_access_request(text, text) to authenticated; +grant execute on function public.glm_hub_decide_access_request(text, text) to service_role; + +-- -------------------------------------------------------------------------- +-- 7. REALTIME DEL CATÁLOGO +-- -------------------------------------------------------------------------- + +-- Hace que una aplicación creada/editada/eliminada por un administrador +-- aparezca en las sesiones abiertas de los demás usuarios sin recargar. +do $$ +begin + if exists ( + select 1 from pg_publication where pubname = 'supabase_realtime' + ) and not exists ( + select 1 + from pg_publication_tables + where pubname = 'supabase_realtime' + and schemaname = 'public' + and tablename = 'glm_hub_apps' + ) then + execute 'alter publication supabase_realtime add table public.glm_hub_apps'; + end if; +end; +$$; + +commit; + +-- ============================================================================ +-- OPERACIONES DE ADMINISTRACIÓN (EJECUTAR DESDE SQL EDITOR) +-- ============================================================================ + +-- DAR ACCESO A UN USUARIO NORMAL: +-- insert into public.glm_hub_authorized_users (email, full_name, role, is_active) +-- values ('usuario@gomezleemarketing.com', 'Nombre del usuario', 'member', true) +-- on conflict (email) do update +-- set full_name = excluded.full_name, role = 'member', is_active = true; + +-- QUITAR ACCESO SIN BORRAR EL REGISTRO: +-- update public.glm_hub_authorized_users +-- set is_active = false +-- where email = 'usuario@gomezleemarketing.com'; + +-- REACTIVAR ACCESO: +-- update public.glm_hub_authorized_users +-- set is_active = true +-- where email = 'usuario@gomezleemarketing.com'; + +-- VER SOLICITUDES DE ACCESO: +-- select requester_name, requester_email, app_name, status, decided_by_name, decided_by_email, created_at, decided_at +-- from public.glm_hub_access_requests +-- order by created_at desc; + +-- VER EL CATÁLOGO COMPARTIDO: +-- select id, name, category, visibility, url, icon_url, updated_at +-- from public.glm_hub_apps +-- order by name; diff --git a/dist/assets/index-BT8YJLeR.css b/dist/assets/index-BT8YJLeR.css new file mode 100644 index 0000000..a50405b --- /dev/null +++ b/dist/assets/index-BT8YJLeR.css @@ -0,0 +1 @@ +:root{font-family:Arial,sans-serif;color:#4a4a4a;background:#fff;font-synthesis:none;text-rendering:optimizeLegibility;--glm-blue: #4f758b;--glm-blue-mid: #5b7f95;--glm-steel: #6b8fa3;--glm-green: #6cc24a;--glm-lime: #c4d600;--glm-teal: #2c6e6f;--glm-orange: #ff6a13;--text: #4a4a4a;--muted: #6b6b6b;--line: #d0d0d0;--paper: #ffffff;--soft: #f5f5f5;--green-soft: #eef6e8;--danger: #b13b2d;--sidebar-width: 244px}*{box-sizing:border-box}html{min-width:320px;min-height:100%;scroll-behavior:smooth}body{min-width:320px;min-height:100vh;margin:0;background:var(--paper);color:var(--text)}button,input,textarea,select{font:inherit}button,a,input,textarea,select{-webkit-tap-highlight-color:transparent}button{color:inherit}button:not(:disabled){cursor:pointer}button:disabled{cursor:wait;opacity:.66}img{display:block;max-width:100%}::selection{background:#eef6e8;color:#2c4a59}:focus-visible{outline:3px solid var(--glm-green);outline-offset:3px}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.brand-logo{width:202px;height:auto}.brand-logo--compact{width:154px}.section-kicker,.eyebrow{margin:0 0 10px;color:var(--glm-blue);font-size:.72rem;font-weight:700;letter-spacing:.17em;line-height:1.2}.primary-button,.secondary-button,.danger-button{min-height:48px;display:inline-flex;align-items:center;justify-content:center;gap:10px;border:1px solid transparent;border-radius:3px;padding:0 20px;font-weight:700;transition:background-color .16s ease,color .16s ease,border-color .16s ease,transform .16s ease}.primary-button{position:relative;overflow:hidden;background:var(--glm-blue);color:#fff}.primary-button:after{content:"";position:absolute;left:0;bottom:0;width:100%;height:3px;background:var(--glm-green)}.primary-button:hover{background:#3f6378;transform:translateY(-1px)}.primary-button--full{width:100%}.primary-button--full svg:last-child{margin-left:auto}.secondary-button{background:#fff;border-color:var(--glm-blue);color:var(--glm-blue)}.secondary-button:hover{background:var(--soft)}.secondary-button--quiet{border-color:var(--line);color:var(--text)}.danger-button{background:transparent;border-color:#e4c6c1;color:var(--danger)}.danger-button:hover{background:#fdecea;border-color:var(--danger)}.danger-button--solid{background:var(--danger);border-color:var(--danger);color:#fff}.text-button{min-height:40px;border:0;border-bottom:1px solid currentColor;padding:0;background:transparent;color:var(--glm-blue);font-weight:700}.text-button--muted{color:var(--muted);font-size:.78rem}.text-button--danger{color:var(--danger);font-size:.78rem}.icon-button,.favorite-button{width:44px;height:44px;display:inline-grid;flex:0 0 auto;place-items:center;border:1px solid var(--line);border-radius:50%;background:#fff;color:var(--glm-blue)}.icon-button:hover,.favorite-button:hover{border-color:var(--glm-blue);background:var(--soft)}.icon-button--quiet{width:36px;height:36px;border-color:transparent;background:transparent}.icon-button--accent{border-radius:3px;background:var(--glm-blue);border-color:var(--glm-blue);color:#fff}.favorite-button.is-favorite{border-color:#d7e58f;background:#f8f9df;color:#718000}.app-mark{position:relative;display:inline-grid;flex:0 0 auto;place-items:center;overflow:visible;background:transparent}.app-mark--small{width:44px;height:44px}.app-mark--regular{width:58px;height:58px}.app-mark--large{width:72px;height:72px}.app-mark img{display:block;width:100%;height:100%;object-fit:contain}.app-mark__placeholder{color:#a7a7a7}.login-shell{min-height:100vh;display:grid;grid-template-columns:minmax(0,1.28fr) minmax(420px,.72fr);background:#fff}.login-story{position:relative;min-height:100vh;display:flex;flex-direction:column;overflow:hidden;border-right:1px solid var(--line);padding:42px clamp(40px,5vw,82px) 36px;background:#fff}.login-story:before{content:"";position:absolute;top:0;left:clamp(40px,5vw,82px);width:3px;height:126px;background:var(--glm-green)}.login-story__topline{position:relative;z-index:2;display:flex;align-items:flex-start;justify-content:space-between;gap:24px;padding-left:18px}.login-story__edition{padding-top:10px;color:var(--glm-blue);font-size:.7rem;font-weight:700;letter-spacing:.18em}.login-story__content{position:relative;z-index:2;width:min(760px,92%);margin:auto 0;padding:80px 0 76px}.login-story h1{margin:0;color:var(--glm-blue);font-family:Arial Black,Arial,sans-serif;font-size:clamp(3.5rem,6.35vw,7.7rem);font-weight:900;letter-spacing:-.075em;line-height:.85}.login-story h1 span{display:block;margin-top:.14em;color:var(--text);font-family:Arial,sans-serif;font-size:.49em;font-weight:400;letter-spacing:-.052em;line-height:1}.login-story__lede{max-width:540px;margin:36px 0 0;padding-left:20px;border-left:3px solid var(--glm-green);color:var(--text);font-size:clamp(1rem,1.35vw,1.2rem);line-height:1.55}.login-story__signal{position:relative;z-index:2;display:grid;grid-template-columns:auto minmax(40px,1fr) auto;align-items:center;gap:14px;max-width:530px;color:var(--glm-blue);font-size:.7rem;font-weight:700;letter-spacing:.14em}.login-story__signal-line{height:1px;background:var(--line)}.login-story__signal-line:before{content:"";display:block;width:18%;height:2px;background:var(--glm-green)}.login-story__place{position:relative;z-index:2;margin:10px 0 0;color:var(--muted);font-size:.65rem;letter-spacing:.16em}.login-story__type{position:absolute;right:-.06em;bottom:-.28em;color:var(--soft);font-family:Arial Black,Arial,sans-serif;font-size:clamp(13rem,27vw,31rem);font-weight:900;letter-spacing:-.13em;line-height:1;pointer-events:none;-webkit-user-select:none;user-select:none}.login-panel{min-height:100vh;display:grid;place-items:center;padding:48px clamp(34px,4vw,72px);background:var(--soft)}.login-panel__inner{width:min(100%,460px)}.login-panel__heading h2{margin:0 0 10px;color:var(--glm-blue);font-family:Arial Black,Arial,sans-serif;font-size:clamp(2rem,3vw,3rem);letter-spacing:-.045em;line-height:1}.login-panel__heading>p:last-child{margin:0;color:var(--muted);line-height:1.55}.login-form{margin-top:34px}.form-field{display:grid;gap:8px;margin-bottom:20px}.form-field label{color:var(--text);font-size:.81rem;font-weight:700}.form-field label>span{margin-left:6px;color:var(--muted);font-size:.72rem;font-weight:400}.form-field label>.required-mark,.editor-form__section-title>.required-mark{margin-left:4px;color:var(--danger);font-size:1em;font-weight:700}.form-field__label-row{display:flex;align-items:center;justify-content:space-between;gap:16px}.form-field__label-row>span{color:var(--muted);font-size:.7rem}.form-field input,.form-field textarea,.form-field select{width:100%;border:1px solid var(--line);border-radius:3px;background:#fff;color:var(--text);transition:border-color .14s ease,box-shadow .14s ease}.form-field input,.form-field select{height:50px;padding:0 14px}.form-field textarea{min-height:94px;resize:vertical;padding:13px 14px;line-height:1.5}.form-field input::placeholder,.form-field textarea::placeholder{color:#8a8a8a}.form-field input:hover,.form-field textarea:hover,.form-field select:hover{border-color:#97a9b3}.form-field input:focus,.form-field textarea:focus,.form-field select:focus{border-color:var(--glm-blue);box-shadow:inset 3px 0 0 var(--glm-green);outline:none}.form-field input[aria-invalid=true],.form-field textarea[aria-invalid=true]{border-color:var(--danger)}.password-field,.url-field{position:relative}.password-field input{padding-right:50px}.password-field__toggle{position:absolute;top:3px;right:3px;width:44px;height:44px;display:grid;place-items:center;border:0;background:transparent;color:var(--glm-blue)}.form-error,.field-error{color:var(--danger);font-size:.77rem;line-height:1.4}.form-error{margin:-4px 0 18px;padding:11px 12px;border-left:3px solid var(--danger);background:#fdecea}.check-control{min-height:44px;display:flex;align-items:center;gap:10px;margin:2px 0 18px;color:var(--text);font-size:.8rem;cursor:pointer}.check-control input{position:absolute;width:1px;height:1px;overflow:hidden;opacity:0}.check-control>span{width:20px;height:20px;display:grid;place-items:center;border:1px solid var(--line);border-radius:2px;background:#fff;color:transparent}.check-control input:checked+span{border-color:var(--glm-blue);background:var(--glm-blue);color:#fff}.check-control input:focus-visible+span{outline:3px solid var(--glm-green);outline-offset:3px}.demo-access{margin-top:30px}.demo-access__label,.app-preview__label{display:flex;align-items:center;gap:12px;color:var(--muted);font-size:.62rem;font-weight:700;letter-spacing:.14em}.demo-access__rule,.app-preview__rule{height:1px;flex:1;background:var(--line)}.demo-access__options{margin-top:10px;border-top:1px solid var(--line)}.demo-access__options button{width:100%;min-height:62px;display:grid;grid-template-columns:24px 1fr 20px;align-items:center;gap:12px;border:0;border-bottom:1px solid var(--line);padding:8px 4px;background:transparent;color:var(--glm-blue);text-align:left}.demo-access__options button:hover{padding-left:10px;background:#fff}.demo-access__options button span{display:grid;gap:3px}.demo-access__options button strong{color:var(--text);font-size:.8rem}.demo-access__options button small{color:var(--muted);font-size:.69rem}.demo-note{margin:18px 0 0;color:var(--muted);font-size:.68rem;line-height:1.45}.app-shell{min-height:100vh;background:#fff}.sidebar{position:fixed;top:0;bottom:0;left:0;z-index:20;width:var(--sidebar-width);display:flex;flex-direction:column;border-right:1px solid var(--line);background:#fff}.sidebar__brand{height:116px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:7px;border-bottom:1px solid var(--line);padding:0 26px}.sidebar__brand span{color:var(--glm-blue);font-size:.58rem;font-weight:700;letter-spacing:.28em}.sidebar__nav{display:grid;gap:4px;padding:28px 16px}.sidebar__nav button{position:relative;min-height:52px;display:flex;align-items:center;gap:14px;border:0;border-radius:2px;padding:0 14px;background:transparent;color:var(--muted);font-size:.82rem;font-weight:700;text-align:left}.sidebar__nav button:hover{background:var(--soft);color:var(--glm-blue)}.sidebar__nav button.is-active{background:var(--green-soft);color:var(--glm-blue)}.sidebar__meta{padding:22px 28px;border-top:1px solid var(--line)}.sidebar__meta p{margin:12px 0 0;color:var(--muted);font-size:.6rem;font-weight:700;letter-spacing:.14em}.sidebar__meta p+p{margin-top:4px}.sidebar__account{margin-top:auto;min-height:88px;display:flex;align-items:center;gap:10px;border-top:1px solid var(--line);padding:16px}.avatar{width:38px;height:38px;display:grid;flex:0 0 auto;place-items:center;overflow:hidden;border-radius:50%;background:var(--glm-blue);color:#fff;font-size:.67rem;font-weight:700;letter-spacing:.03em}.avatar img{display:block;width:100%;height:100%;object-fit:cover}.sidebar__account-copy{min-width:0;flex:1;display:grid;gap:3px}.sidebar__account-copy strong,.sidebar__account-copy small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sidebar__account-copy strong{color:var(--text);font-size:.77rem}.sidebar__account-copy small{color:var(--muted);font-size:.62rem}.sidebar__account .icon-button{width:38px;height:38px;border:0}.mobile-header,.mobile-nav{display:none}.page-content{min-height:100vh;margin-left:var(--sidebar-width);padding:42px clamp(36px,5vw,82px) 72px}.page-header{display:flex;align-items:flex-end;justify-content:space-between;gap:30px;padding-bottom:30px;border-bottom:1px solid var(--line)}.page-header__meta{margin:0 0 9px;color:var(--glm-blue);font-size:.72rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase}.page-header h1{margin:0;color:var(--glm-blue);font-family:Arial Black,Arial,sans-serif;font-size:clamp(2.4rem,4.2vw,4.85rem);letter-spacing:-.065em;line-height:.9}.page-header__aside{display:grid;justify-items:end;padding-bottom:4px}.page-header__aside>span:last-child{color:var(--muted);font-size:.82rem;font-weight:700;line-height:1.35;white-space:nowrap}.inline-alert{margin-top:18px;border-left:3px solid var(--glm-orange);padding:12px 14px;background:#fff8e1;color:var(--text);font-size:.82rem}.search-stage{position:relative;overflow:hidden;display:grid;grid-template-columns:minmax(260px,.62fr) minmax(360px,1.38fr);align-items:end;gap:40px;margin-top:38px;border-left:3px solid var(--glm-green);padding:34px 38px 32px;background:var(--soft)}.search-stage__copy,.hub-search{position:relative;z-index:2}.search-stage h2,.section-heading h2,.admin-bridge h2,.access-request-bridge h2,.admin-list h2,.app-editor h2{margin:0;color:var(--glm-blue);font-family:Arial Black,Arial,sans-serif;letter-spacing:-.04em}.search-stage h2{max-width:360px;font-size:clamp(2rem,3.3vw,3.5rem);line-height:.98}.hub-search{min-height:62px;display:grid;grid-template-columns:24px 1fr auto;align-items:center;gap:13px;border-bottom:2px solid var(--glm-blue);background:#fff;padding:0 16px;color:var(--glm-blue)}.hub-search:focus-within{border-bottom-color:var(--glm-green);box-shadow:0 0 0 1px var(--line)}.hub-search input{width:100%;height:60px;border:0;outline:0;background:transparent;color:var(--text);font-size:.96rem}.hub-search input::placeholder{color:#747474}.hub-search button{width:40px;height:40px;display:grid;place-items:center;border:0;background:transparent;color:var(--glm-blue)}.hub-search kbd{border:1px solid var(--line);border-radius:3px;padding:4px 7px;background:var(--soft);color:var(--muted);font-family:Arial,sans-serif;font-size:.65rem}.search-stage__coordinate{position:absolute;top:-.31em;right:.02em;color:#e9e9e9;font-family:Arial Black,Arial,sans-serif;font-size:clamp(5.5rem,10vw,10rem);letter-spacing:-.08em;line-height:1;-webkit-user-select:none;user-select:none}.quick-section,.directory-section{margin-top:64px}.section-heading{display:flex;align-items:flex-end;justify-content:space-between;gap:28px;margin-bottom:22px}.section-heading h2{font-size:clamp(1.75rem,2.5vw,2.8rem)}.section-heading>p{max-width:340px;margin:0 0 2px;color:var(--muted);font-size:.82rem;line-height:1.5;text-align:right}.quick-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));align-items:stretch;gap:14px}.quick-launch{--card-accent: var(--glm-green);min-width:0;min-height:360px;display:flex;flex-direction:column;border:1px solid var(--line);border-top:3px solid var(--glm-green);border-radius:2px;padding:28px;background:#fff;transition:border-color .16s ease,transform .16s ease}.quick-launch:hover{border-right-color:var(--glm-blue);border-bottom-color:var(--glm-blue);border-left-color:var(--glm-blue);transform:translateY(-3px)}.quick-launch__top{display:flex;align-items:flex-start;justify-content:space-between;gap:20px}.quick-launch__copy{flex:1;margin-top:48px}.quick-launch__category{color:var(--glm-blue);font-size:.64rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase}.quick-launch h3,.preview-card h3{margin:9px 0 8px;color:var(--text);font-family:Arial Black,Arial,sans-serif;font-size:1.35rem;letter-spacing:-.035em;line-height:1.05}.quick-launch h3{color:var(--glm-blue);font-size:1.55rem}.quick-launch p,.preview-card p{margin:0;color:var(--muted);font-size:.82rem;line-height:1.5}.quick-launch__action{width:100%;min-height:46px;display:flex;align-items:center;justify-content:space-between;gap:14px;margin-top:auto;border:0;border-top:1px solid var(--line);padding:13px 0 0;background:transparent;color:var(--glm-blue);font-size:.76rem;font-weight:700;text-align:left}.quick-launch__action:hover{color:var(--glm-teal)}.section-heading--directory{padding-bottom:18px;border-bottom:1px solid var(--line)}.directory-count{display:flex;align-items:baseline;gap:7px}.directory-count strong{color:var(--glm-blue);font-family:Arial Black,Arial,sans-serif;font-size:2rem;letter-spacing:-.06em}.directory-count span{color:var(--muted);font-size:.7rem}.filter-bar{display:flex;align-items:center;justify-content:space-between;gap:20px;margin-bottom:18px}.category-filters{display:flex;align-items:center;gap:4px}.category-filters button,.favorites-filter{min-height:44px;border:0;border-bottom:2px solid transparent;padding:0 12px;background:transparent;color:var(--muted);font-size:.74rem;font-weight:700;white-space:nowrap}.category-filters button:hover,.favorites-filter:hover{color:var(--glm-blue)}.category-filters button.is-active{border-bottom-color:var(--glm-green);color:var(--glm-blue)}.favorites-filter{display:inline-flex;align-items:center;gap:8px}.favorites-filter.is-active{border-bottom-color:var(--glm-lime);color:#617000}.directory-list{border-top:1px solid var(--text)}.pagination{display:flex;align-items:center;justify-content:center;gap:8px;margin-top:24px}.pagination__pages{display:flex;align-items:center;justify-content:center;gap:6px}.pagination__arrow,.pagination__page{width:36px;height:36px;display:grid;flex:0 0 auto;place-items:center;border:1px solid var(--line);border-radius:3px;background:#fff;color:var(--glm-blue);font-size:.74rem;font-weight:700;transition:background-color .14s ease,border-color .14s ease,color .14s ease}.pagination__arrow:hover:not(:disabled),.pagination__page:hover:not(.is-active){border-color:var(--glm-blue);background:var(--soft)}.pagination__page.is-active{border-color:var(--glm-blue);background:var(--glm-blue);color:#fff}.pagination__arrow:disabled{cursor:not-allowed;opacity:.35}.pagination__ellipsis{width:24px;color:var(--muted);font-size:.8rem;line-height:1;text-align:center}.pagination--compact{gap:5px}.pagination--compact .pagination__pages{gap:4px}.pagination--compact .pagination__arrow,.pagination--compact .pagination__page{width:30px;height:30px;font-size:.66rem}.pagination--compact .pagination__ellipsis{width:16px;font-size:.7rem}.directory-row{min-height:92px;display:grid;grid-template-columns:42px 44px minmax(220px,1fr) 110px 128px 44px 86px;align-items:center;gap:14px;border-bottom:1px solid var(--line);padding:14px 0;transition:background-color .14s ease,padding .14s ease}.directory-row:hover{padding-right:10px;padding-left:10px;background:var(--soft)}.directory-row__number{color:#919191;font-family:Arial Black,Arial,sans-serif;font-size:.65rem;letter-spacing:.06em}.directory-row__copy{min-width:0}.directory-row__copy h3{margin:0 0 5px;color:var(--text);font-size:.92rem;line-height:1.2}.directory-row__copy p{overflow:hidden;margin:0;color:var(--muted);font-size:.72rem;line-height:1.4;text-overflow:ellipsis;white-space:nowrap}.directory-row__category{color:var(--glm-blue);font-size:.65rem;font-weight:700}.availability{display:inline-flex;align-items:center;gap:7px;color:var(--muted);font-size:.67rem;font-weight:700;white-space:nowrap}.availability>span{width:7px;height:7px;flex:0 0 auto;border-radius:50%;background:var(--line)}.availability--ready>span{background:var(--glm-green);box-shadow:0 0 0 3px var(--green-soft)}.availability--pending>span{background:var(--glm-orange);box-shadow:0 0 0 3px #fff1e8}.directory-row .favorite-button{width:40px;height:40px;border:0;background:transparent}.directory-row__open{min-height:42px;display:flex;align-items:center;justify-content:flex-end;gap:7px;border:0;background:transparent;color:var(--glm-blue);font-size:.74rem;font-weight:700}.empty-state{min-height:280px;display:grid;grid-template-columns:minmax(110px,.35fr) minmax(260px,.65fr);align-items:center;gap:40px;border-top:1px solid var(--text);border-bottom:1px solid var(--line);padding:38px}.empty-state__mark{color:var(--soft);font-family:Arial Black,Arial,sans-serif;font-size:10rem;letter-spacing:-.1em;line-height:.7}.empty-state h3{margin:0 0 8px;color:var(--glm-blue);font-family:Arial Black,Arial,sans-serif;font-size:1.6rem;letter-spacing:-.04em}.empty-state p:not(.section-kicker){margin:0 0 18px;color:var(--muted);font-size:.84rem;line-height:1.5}.admin-bridge{position:relative;overflow:hidden;min-height:190px;display:grid;grid-template-columns:minmax(70px,.25fr) minmax(300px,1fr) auto;align-items:center;gap:30px;margin-top:70px;border-top:1px solid var(--text);border-bottom:3px solid var(--glm-green);padding:30px 8px}.admin-bridge__number{color:var(--glm-green);font-family:Arial Black,Arial,sans-serif;font-size:7rem;line-height:.7}.admin-bridge h2{font-size:2rem}.admin-bridge p:not(.section-kicker){margin:8px 0 0;color:var(--muted);font-size:.84rem;line-height:1.5}.access-request-bridge{position:relative;min-height:190px;display:grid;grid-template-columns:minmax(70px,.25fr) minmax(300px,1fr) auto;align-items:center;gap:30px;margin-top:70px;border-top:1px solid var(--text);border-bottom:3px solid var(--glm-green);padding:30px 8px}.access-request-bridge__number{color:var(--glm-green);font-family:Arial Black,Arial,sans-serif;font-size:6.5rem;line-height:.72}.access-request-bridge h2{font-size:2rem}.access-request-bridge p:not(.section-kicker){margin:8px 0 0;color:var(--muted);font-size:.84rem;line-height:1.5}.access-request-dialog{width:min(560px,calc(100vw - 32px));border:0;border-top:4px solid var(--glm-green);border-radius:3px;padding:34px;color:var(--text);box-shadow:0 24px 80px #2d363b38}.access-request-dialog::backdrop{background:#2330378f}.access-request-dialog__close{position:absolute;top:12px;right:12px;width:40px;height:40px;display:grid;place-items:center;border:0;background:transparent;color:var(--muted)}.access-request-dialog h2{margin:0;color:var(--glm-blue);font-family:Arial Black,Arial,sans-serif;font-size:2rem;letter-spacing:-.04em}.access-request-dialog__intro{margin:10px 0 34px;color:var(--muted);font-size:.82rem;line-height:1.55}.access-request-dialog form{display:grid;gap:20px;margin-top:4px}.access-request-dialog__actions{display:flex;justify-content:flex-end;gap:10px;margin-top:8px;padding-top:22px;border-top:1px solid var(--line)}.back-button{min-height:40px;display:inline-flex;align-items:center;gap:8px;margin-bottom:18px;border:0;border-bottom:1px solid var(--line);padding:0;background:transparent;color:var(--glm-blue);font-size:.75rem;font-weight:700}.admin-header{align-items:center}.admin-header__note{display:flex;align-items:center;gap:12px;border-left:3px solid var(--glm-green);padding:10px 0 10px 14px;color:var(--glm-blue)}.admin-header__note span{display:grid;gap:3px}.admin-header__note strong{color:var(--text);font-size:.74rem}.admin-header__note small{color:var(--muted);font-size:.66rem}.admin-workspace{min-height:680px;display:grid;grid-template-columns:300px minmax(0,1fr);margin-top:34px;border:1px solid var(--line)}.admin-list{min-width:0;display:flex;flex-direction:column;border-right:1px solid var(--line);background:var(--soft)}.admin-list__top{min-height:94px;display:flex;align-items:center;justify-content:space-between;gap:20px;border-bottom:1px solid var(--line);padding:20px}.admin-list h2{font-size:1.25rem}.admin-list__search{display:grid;grid-template-columns:20px 1fr;align-items:center;gap:9px;margin:14px;border:1px solid var(--line);background:#fff;padding:0 11px;color:var(--glm-blue)}.admin-list__search input{width:100%;height:42px;border:0;outline:0;background:transparent;font-size:.77rem}.admin-list__items{min-height:0;flex:1;overflow-y:auto;border-top:1px solid var(--line)}.admin-list__items>button,.admin-list__item-row{position:relative;width:100%;min-height:70px;display:grid;grid-template-columns:44px minmax(0,1fr) auto;align-items:center;gap:11px;border:0;border-bottom:1px solid var(--line);padding:10px 14px;background:transparent;text-align:left;cursor:pointer}.admin-list__items>button:hover,.admin-list__item-row:hover{background:#fff}.admin-list__items>button.is-active,.admin-list__item-row.is-active{background:#fff}.admin-list__items>button.is-active:before,.admin-list__item-row.is-active:before{content:"";position:absolute;top:0;bottom:0;left:0;width:3px;background:var(--glm-green)}.admin-list__item-info{min-width:0;display:flex;flex-direction:column;gap:3px;text-align:left}.admin-list__item-info strong{display:block;color:var(--text);font-size:.76rem;font-weight:700;line-height:1.25}.admin-list__item-info small{display:block;color:var(--muted);font-size:.64rem;font-weight:400;line-height:1.2}.admin-list__item-actions{display:flex;align-items:center;gap:8px}.admin-list__delete-icon{width:28px;height:28px;display:grid;place-items:center;border:0;border-radius:3px;background:transparent;color:#a0a0a0;transition:color .14s ease,background-color .14s ease}.admin-list__delete-icon:hover{color:var(--danger);background:#fdecea}.admin-list__items strong,.admin-list__items small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.admin-list__new-icon{width:38px;height:38px;display:grid;place-items:center;border:1px dashed var(--glm-blue);border-radius:3px;color:var(--glm-blue)}.mini-status{color:var(--glm-blue);font-size:.57rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.mini-status.is-hidden{color:var(--muted)}.admin-list>.pagination{flex:0 0 auto;justify-content:space-between;margin-top:0;border-top:1px solid var(--line);padding:10px 12px;background:#fff}.app-editor{min-width:0;padding:28px clamp(24px,3.4vw,52px) 44px}.app-editor__heading{display:flex;align-items:flex-start;justify-content:space-between;gap:24px;border-bottom:1px solid var(--line);padding-bottom:20px}.app-editor h2{font-size:clamp(1.5rem,2.4vw,2.35rem)}.dirty-state{display:inline-flex;align-items:center;gap:7px;color:var(--muted);font-size:.65rem;font-weight:700;white-space:nowrap}.dirty-state>span{width:7px;height:7px;border-radius:50%;background:var(--glm-green)}.dirty-state.is-dirty>span{background:var(--glm-orange)}.editor-layout{display:grid;grid-template-columns:minmax(0,1fr) 260px;align-items:start;gap:clamp(28px,4vw,54px);margin-top:28px}.editor-form__section{display:grid;gap:18px;margin:0;border-bottom:1px solid var(--line);padding:0 0 28px}.editor-form__section+.editor-form__section{padding-top:28px}.editor-form__section-title{margin:0 0 2px;color:var(--glm-blue);font-size:.72rem;font-weight:700;letter-spacing:.12em;line-height:1.2;text-transform:uppercase}.editor-form .form-field{margin:0}.form-split{display:grid;grid-template-columns:1fr 1fr;gap:16px}.field-meta{display:flex;justify-content:space-between;gap:14px;color:var(--muted);font-size:.66rem;line-height:1.4}.field-meta>span:last-child{flex:0 0 auto}.field-meta--counter-only{justify-content:flex-end}.field-help{color:var(--muted);font-size:.67rem;line-height:1.4}.field-error--standalone{margin:0}.icon-config{display:flex;align-items:center;gap:18px}.ai-icon-actions{display:flex;align-items:center}.ai-icon-button{min-height:44px}.icon-dropzone{min-height:80px;display:grid;flex:1;grid-template-columns:42px 1fr;align-items:center;gap:13px;border:1px dashed var(--glm-blue);border-radius:3px;padding:14px;background:var(--soft);cursor:pointer;transition:border-color .14s ease,background-color .14s ease}.icon-dropzone:hover,.icon-dropzone.is-dragging{border-color:var(--glm-green);background:var(--green-soft)}.icon-dropzone input{position:absolute;width:1px;height:1px;overflow:hidden;opacity:0}.icon-dropzone:focus-within{outline:3px solid var(--glm-green);outline-offset:3px}.icon-dropzone__symbol{width:42px;height:42px;display:grid;place-items:center;border-radius:3px;background:#fff;color:var(--glm-blue)}.icon-dropzone>span:last-child{display:grid;gap:4px}.icon-dropzone strong{color:var(--text);font-size:.77rem}.icon-dropzone small{color:var(--muted);font-size:.65rem}.url-field{display:grid;grid-template-columns:24px 1fr;align-items:center;border:1px solid var(--line);border-radius:3px;padding:0 12px;background:#fff;color:var(--glm-blue)}.url-field:focus-within{border-color:var(--glm-blue);box-shadow:inset 3px 0 0 var(--glm-green)}.url-field input{border:0;box-shadow:none;padding:0 3px}.url-field input:focus{box-shadow:none}.editor-actions{display:flex;align-items:center;gap:10px;padding-top:26px}.editor-actions .danger-button{margin-left:auto}.app-preview{position:sticky;top:28px}.preview-card{min-height:302px;display:flex;flex-direction:column;align-items:flex-start;margin-top:14px;border:1px solid var(--line);border-top:3px solid var(--glm-green);padding:22px;background:#fff}.preview-card .app-mark{margin-bottom:36px}.preview-card .availability{margin-top:auto;padding-top:24px}.app-preview__note{margin:12px 0 0;color:var(--muted);font-size:.65rem;line-height:1.45}.confirm-dialog{width:min(440px,calc(100vw - 32px));border:0;border-top:4px solid var(--danger);border-radius:3px;padding:30px;color:var(--text);box-shadow:0 24px 80px #2d363b38}.confirm-dialog::backdrop{background:#2330378f}.confirm-dialog__close{position:absolute;top:12px;right:12px;width:40px;height:40px;display:grid;place-items:center;border:0;background:transparent;color:var(--muted)}.confirm-dialog__icon{width:48px;height:48px;display:grid;place-items:center;margin-bottom:28px;border-radius:50%;background:#fdecea;color:var(--danger)}.confirm-dialog h2{margin:0;color:var(--text);font-family:Arial Black,Arial,sans-serif;font-size:1.6rem;letter-spacing:-.04em}.confirm-dialog>p:not(.section-kicker){margin:12px 0 0;color:var(--muted);font-size:.82rem;line-height:1.55}.confirm-dialog__actions{display:flex;justify-content:flex-end;gap:10px;margin-top:28px}.generation-overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:200;display:grid;place-items:center;padding:24px;background:#162128b8;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px)}.generation-overlay__card{width:min(460px,100%);display:grid;justify-items:center;border-top:4px solid var(--glm-green);padding:38px 34px;background:#fff;box-shadow:0 24px 70px #00000047;text-align:center}.generation-overlay__card h2{margin:10px 0 0;color:var(--glm-blue);font-family:Arial Black,Arial,sans-serif;font-size:1.75rem;letter-spacing:-.04em}.generation-overlay__card>p:last-child{max-width:350px;margin:12px 0 0;color:var(--muted);font-size:.82rem;line-height:1.5}.generation-spinner{width:40px;height:40px;margin-bottom:20px;border:4px solid var(--line);border-top-color:var(--glm-green);border-radius:50%;animation:glm-spin .8s linear infinite}.catalog-loading{min-height:100vh;display:grid;place-items:center;align-content:center;gap:18px;background:#fff;color:var(--glm-blue);font-size:.85rem;font-weight:700}.catalog-loading .brand-logo{width:150px;margin-bottom:8px}.catalog-loading .generation-spinner,.catalog-loading p{margin:0}@keyframes glm-spin{to{transform:rotate(360deg)}}.toast-stack{position:fixed;right:24px;bottom:24px;z-index:100;width:min(390px,calc(100vw - 32px));display:grid;gap:10px}.toast{position:relative;display:grid;grid-template-columns:3px 1fr 36px;align-items:center;gap:14px;overflow:hidden;border:1px solid var(--line);border-radius:3px;padding:13px 10px 13px 0;background:#fff;box-shadow:0 16px 44px #32444e29}.toast__signal{align-self:stretch;background:var(--glm-green)}.toast>div,.toast .icon-button{align-self:center}.toast--info .toast__signal{background:var(--glm-blue)}.toast--error .toast__signal{background:var(--danger)}.toast strong{display:block;margin-top:0;color:var(--text);font-size:.8rem}.toast p{margin:4px 0 0;color:var(--muted);font-size:.7rem;line-height:1.45}@media(max-width:1240px){:root{--sidebar-width: 214px}.sidebar__brand{padding:0 20px}.sidebar__brand .brand-logo{width:140px}.sidebar__account-copy{display:none}.directory-row{grid-template-columns:34px 44px minmax(190px,1fr) 112px 44px 80px}.directory-row__category{display:none}.editor-layout{grid-template-columns:minmax(0,1fr) 230px;gap:28px}}@media(max-width:1360px){.editor-layout{grid-template-columns:1fr}.app-preview{position:static}.preview-card{width:100%;min-height:260px}}@media(max-width:1040px){.page-content{padding-right:32px;padding-left:32px}.search-stage{grid-template-columns:1fr;align-items:start;gap:24px}.quick-grid{grid-template-columns:1fr 1fr}.directory-row{grid-template-columns:34px 44px minmax(160px,1fr) 108px 44px 44px}.directory-row__open span{display:none}.admin-workspace{grid-template-columns:250px minmax(0,1fr)}}@media(max-width:860px){:root{--mobile-header-height: 72px;--mobile-nav-height: 72px}.login-shell{grid-template-columns:1fr}.login-story{min-height:540px;border-right:0;border-bottom:1px solid var(--line)}.login-story__content{margin:auto 0 0;padding:72px 0 54px}.login-story__type{right:-.03em;bottom:-.3em;font-size:20rem}.login-panel{min-height:auto;padding-top:64px;padding-bottom:64px}.sidebar{display:none}.mobile-header{position:fixed;top:0;right:0;left:0;z-index:30;height:var(--mobile-header-height);display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--line);padding:10px 22px;background:#fffffff5}.mobile-header:before{content:"";position:absolute;top:0;left:0;width:94px;height:3px;background:var(--glm-green)}.mobile-header .brand-logo{width:135px}.mobile-nav{position:fixed;right:0;bottom:0;left:0;z-index:30;min-height:var(--mobile-nav-height);display:flex;align-items:stretch;justify-content:center;border-top:1px solid var(--line);background:#fffffffa}.mobile-nav button{min-width:92px;min-height:66px;display:grid;place-items:center;align-content:center;gap:5px;border:0;border-top:3px solid transparent;background:transparent;color:var(--muted);font-size:.62rem;font-weight:700}.mobile-nav button.is-active{border-top-color:var(--glm-green);color:var(--glm-blue)}.page-content{margin-left:0;padding-top:calc(var(--mobile-header-height) + 34px);padding-bottom:calc(var(--mobile-nav-height) + 42px)}.filter-bar{align-items:flex-start;flex-direction:column}.category-filters{width:100%;overflow-x:auto;padding-bottom:3px}.directory-row{grid-template-columns:44px minmax(150px,1fr) 108px 44px 44px}.directory-row__number{display:none}.admin-bridge,.access-request-bridge{grid-template-columns:80px 1fr}.admin-bridge .secondary-button,.access-request-bridge .secondary-button{grid-column:2;justify-self:start}.admin-workspace{grid-template-columns:1fr}.admin-list{max-height:360px;border-right:0;border-bottom:1px solid var(--line)}.admin-list__items{max-height:210px}}@media(max-width:620px){.login-story{min-height:480px;padding:26px 22px 24px}.login-story:before{left:22px;height:90px}.login-story__topline{padding-left:12px}.login-story .brand-logo{width:160px}.login-story__edition{font-size:.58rem}.login-story__content{width:100%;padding:62px 0 42px}.login-story h1{font-size:clamp(3.25rem,15.5vw,5rem)}.login-story__lede{margin-top:26px;font-size:.95rem}.login-story__signal,.login-story__place{font-size:.54rem}.login-story__type{font-size:12rem}.login-panel{padding:48px 22px}.page-content{padding-right:18px;padding-left:18px}.page-header{padding-bottom:23px}.page-header h1{font-size:2.7rem}.page-header__aside,.admin-header__note{display:none}.search-stage{margin-top:26px;padding:26px 18px 22px}.search-stage__coordinate{opacity:.58}.hub-search{grid-template-columns:22px 1fr auto;min-height:58px;padding:0 12px}.hub-search input{min-width:0;font-size:.84rem}.hub-search kbd{display:none}.pagination{gap:4px}.pagination__pages{gap:3px}.pagination__arrow,.pagination__page{width:32px;height:32px}.quick-section,.directory-section{margin-top:48px}.section-heading{align-items:flex-start;flex-direction:column;gap:12px}.section-heading>p{text-align:left}.section-heading--directory{flex-direction:row;align-items:flex-end}.quick-grid{grid-template-columns:1fr}.quick-launch{min-height:330px;padding:20px}.quick-launch__copy{margin-top:28px}.directory-row{min-height:86px;grid-template-columns:44px minmax(0,1fr) 40px 38px;gap:9px}.directory-row__category,.directory-row .availability{display:none}.directory-row__copy p{max-width:100%}.directory-row .favorite-button,.directory-row__open{width:38px;height:38px;min-height:38px}.empty-state{grid-template-columns:1fr;gap:16px;padding:30px 18px}.empty-state__mark{font-size:6rem}.admin-bridge,.access-request-bridge{grid-template-columns:54px 1fr;gap:16px}.admin-bridge__number,.access-request-bridge__number{font-size:5rem}.admin-bridge h2,.access-request-bridge h2{font-size:1.65rem}.admin-bridge .secondary-button,.access-request-bridge .secondary-button{grid-column:1 / -1;width:100%}.access-request-dialog{padding:28px 20px 22px}.access-request-dialog__actions{flex-direction:column-reverse}.access-request-dialog__actions button{width:100%}.admin-page{padding-right:12px;padding-left:12px}.admin-workspace{margin-top:26px}.app-editor{padding:24px 16px 36px}.app-editor__heading{align-items:flex-start;flex-direction:column;gap:12px}.form-split{grid-template-columns:1fr}.icon-config{align-items:flex-start;flex-direction:column}.icon-dropzone{width:100%}.editor-actions{align-items:stretch;flex-direction:column}.editor-actions .danger-button{margin-left:0}.app-preview{grid-template-columns:1fr}.preview-card{width:100%}.confirm-dialog{padding:26px 20px}.confirm-dialog__actions{align-items:stretch;flex-direction:column}.toast-stack{right:16px;bottom:calc(var(--mobile-nav-height, 0px) + 16px);left:16px;width:auto}}@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*,*:before,*:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}} diff --git a/dist/assets/index-xOqwTEny.js b/dist/assets/index-xOqwTEny.js new file mode 100644 index 0000000..1380b9d --- /dev/null +++ b/dist/assets/index-xOqwTEny.js @@ -0,0 +1,179 @@ +(function(){const d=document.createElement("link").relList;if(d&&d.supports&&d.supports("modulepreload"))return;for(const x of document.querySelectorAll('link[rel="modulepreload"]'))r(x);new MutationObserver(x=>{for(const N of x)if(N.type==="childList")for(const C of N.addedNodes)C.tagName==="LINK"&&C.rel==="modulepreload"&&r(C)}).observe(document,{childList:!0,subtree:!0});function v(x){const N={};return x.integrity&&(N.integrity=x.integrity),x.referrerPolicy&&(N.referrerPolicy=x.referrerPolicy),x.crossOrigin==="use-credentials"?N.credentials="include":x.crossOrigin==="anonymous"?N.credentials="omit":N.credentials="same-origin",N}function r(x){if(x.ep)return;x.ep=!0;const N=v(x);fetch(x.href,N)}})();var Ss={exports:{}},Nn={};/** + * @license React + * react-jsx-runtime.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Yd;function Ky(){if(Yd)return Nn;Yd=1;var f=Symbol.for("react.transitional.element"),d=Symbol.for("react.fragment");function v(r,x,N){var C=null;if(N!==void 0&&(C=""+N),x.key!==void 0&&(C=""+x.key),"key"in x){N={};for(var w in x)w!=="key"&&(N[w]=x[w])}else N=x;return x=N.ref,{$$typeof:f,type:r,key:C,ref:x!==void 0?x:null,props:N}}return Nn.Fragment=d,Nn.jsx=v,Nn.jsxs=v,Nn}var Qd;function Jy(){return Qd||(Qd=1,Ss.exports=Ky()),Ss.exports}var c=Jy(),_s={exports:{}},P={};/** + * @license React + * react.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Xd;function ky(){if(Xd)return P;Xd=1;var f=Symbol.for("react.transitional.element"),d=Symbol.for("react.portal"),v=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),x=Symbol.for("react.profiler"),N=Symbol.for("react.consumer"),C=Symbol.for("react.context"),w=Symbol.for("react.forward_ref"),U=Symbol.for("react.suspense"),E=Symbol.for("react.memo"),G=Symbol.for("react.lazy"),B=Symbol.for("react.activity"),J=Symbol.iterator;function ye(o){return o===null||typeof o!="object"?null:(o=J&&o[J]||o["@@iterator"],typeof o=="function"?o:null)}var Z={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},ee=Object.assign,V={};function ue(o,j,q){this.props=o,this.context=j,this.refs=V,this.updater=q||Z}ue.prototype.isReactComponent={},ue.prototype.setState=function(o,j){if(typeof o!="object"&&typeof o!="function"&&o!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,o,j,"setState")},ue.prototype.forceUpdate=function(o){this.updater.enqueueForceUpdate(this,o,"forceUpdate")};function Me(){}Me.prototype=ue.prototype;function Te(o,j,q){this.props=o,this.context=j,this.refs=V,this.updater=q||Z}var ae=Te.prototype=new Me;ae.constructor=Te,ee(ae,ue.prototype),ae.isPureReactComponent=!0;var ve=Array.isArray;function ge(){}var W={H:null,A:null,T:null,S:null},ne=Object.prototype.hasOwnProperty;function Ue(o,j,q){var L=q.ref;return{$$typeof:f,type:o,key:j,ref:L!==void 0?L:null,props:q}}function Je(o,j){return Ue(o.type,j,o.props)}function xe(o){return typeof o=="object"&&o!==null&&o.$$typeof===f}function De(o){var j={"=":"=0",":":"=2"};return"$"+o.replace(/[=:]/g,function(q){return j[q]})}var Xe=/\/+/g;function Q(o,j){return typeof o=="object"&&o!==null&&o.key!=null?De(""+o.key):j.toString(36)}function I(o){switch(o.status){case"fulfilled":return o.value;case"rejected":throw o.reason;default:switch(typeof o.status=="string"?o.then(ge,ge):(o.status="pending",o.then(function(j){o.status==="pending"&&(o.status="fulfilled",o.value=j)},function(j){o.status==="pending"&&(o.status="rejected",o.reason=j)})),o.status){case"fulfilled":return o.value;case"rejected":throw o.reason}}throw o}function S(o,j,q,L,F){var k=typeof o;(k==="undefined"||k==="boolean")&&(o=null);var ie=!1;if(o===null)ie=!0;else switch(k){case"bigint":case"string":case"number":ie=!0;break;case"object":switch(o.$$typeof){case f:case d:ie=!0;break;case G:return ie=o._init,S(ie(o._payload),j,q,L,F)}}if(ie)return F=F(o),ie=L===""?"."+Q(o,0):L,ve(F)?(q="",ie!=null&&(q=ie.replace(Xe,"$&/")+"/"),S(F,j,q,"",function(et){return et})):F!=null&&(xe(F)&&(F=Je(F,q+(F.key==null||o&&o.key===F.key?"":(""+F.key).replace(Xe,"$&/")+"/")+ie)),j.push(F)),1;ie=0;var ke=L===""?".":L+":";if(ve(o))for(var Ae=0;Ae>>1,z=S[R];if(0>>1;Rx(q,M))Lx(F,q)?(S[R]=F,S[L]=M,R=L):(S[R]=q,S[j]=M,R=j);else if(Lx(F,M))S[R]=F,S[L]=M,R=L;else break e}}return g}function x(S,g){var M=S.sortIndex-g.sortIndex;return M!==0?M:S.id-g.id}if(f.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var N=performance;f.unstable_now=function(){return N.now()}}else{var C=Date,w=C.now();f.unstable_now=function(){return C.now()-w}}var U=[],E=[],G=1,B=null,J=3,ye=!1,Z=!1,ee=!1,V=!1,ue=typeof setTimeout=="function"?setTimeout:null,Me=typeof clearTimeout=="function"?clearTimeout:null,Te=typeof setImmediate<"u"?setImmediate:null;function ae(S){for(var g=v(E);g!==null;){if(g.callback===null)r(E);else if(g.startTime<=S)r(E),g.sortIndex=g.expirationTime,d(U,g);else break;g=v(E)}}function ve(S){if(ee=!1,ae(S),!Z)if(v(U)!==null)Z=!0,ge||(ge=!0,De());else{var g=v(E);g!==null&&I(ve,g.startTime-S)}}var ge=!1,W=-1,ne=5,Ue=-1;function Je(){return V?!0:!(f.unstable_now()-UeS&&Je());){var R=B.callback;if(typeof R=="function"){B.callback=null,J=B.priorityLevel;var z=R(B.expirationTime<=S);if(S=f.unstable_now(),typeof z=="function"){B.callback=z,ae(S),g=!0;break t}B===v(U)&&r(U),ae(S)}else r(U);B=v(U)}if(B!==null)g=!0;else{var o=v(E);o!==null&&I(ve,o.startTime-S),g=!1}}break e}finally{B=null,J=M,ye=!1}g=void 0}}finally{g?De():ge=!1}}}var De;if(typeof Te=="function")De=function(){Te(xe)};else if(typeof MessageChannel<"u"){var Xe=new MessageChannel,Q=Xe.port2;Xe.port1.onmessage=xe,De=function(){Q.postMessage(null)}}else De=function(){ue(xe,0)};function I(S,g){W=ue(function(){S(f.unstable_now())},g)}f.unstable_IdlePriority=5,f.unstable_ImmediatePriority=1,f.unstable_LowPriority=4,f.unstable_NormalPriority=3,f.unstable_Profiling=null,f.unstable_UserBlockingPriority=2,f.unstable_cancelCallback=function(S){S.callback=null},f.unstable_forceFrameRate=function(S){0>S||125R?(S.sortIndex=M,d(E,S),v(U)===null&&S===v(E)&&(ee?(Me(W),W=-1):ee=!0,I(ve,M-R))):(S.sortIndex=z,d(U,S),Z||ye||(Z=!0,ge||(ge=!0,De()))),S},f.unstable_shouldYield=Je,f.unstable_wrapCallback=function(S){var g=J;return function(){var M=J;J=g;try{return S.apply(this,arguments)}finally{J=M}}}})(js)),js}var Vd;function Wy(){return Vd||(Vd=1,As.exports=$y()),As.exports}var zs={exports:{}},tt={};/** + * @license React + * react-dom.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Kd;function Fy(){if(Kd)return tt;Kd=1;var f=Rs();function d(U){var E="https://react.dev/errors/"+U;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(f)}catch(d){console.error(d)}}return f(),zs.exports=Fy(),zs.exports}/** + * @license React + * react-dom-client.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var kd;function Py(){if(kd)return Tn;kd=1;var f=Wy(),d=Rs(),v=Iy();function r(e){var t="https://react.dev/errors/"+e;if(1z||(e.current=R[z],R[z]=null,z--)}function q(e,t){z++,R[z]=e.current,e.current=t}var L=o(null),F=o(null),k=o(null),ie=o(null);function ke(e,t){switch(q(k,t),q(F,e),q(L,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?fd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=fd(t),e=rd(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}j(L),q(L,e)}function Ae(){j(L),j(F),j(k)}function et(e){e.memoizedState!==null&&q(ie,e);var t=L.current,l=rd(t,e.type);t!==l&&(q(F,e),q(L,l))}function tl(e){F.current===e&&(j(L),j(F)),ie.current===e&&(j(ie),En._currentValue=M)}var li,Bs;function Tl(e){if(li===void 0)try{throw Error()}catch(l){var t=l.stack.trim().match(/\n( *(at )?)/);li=t&&t[1]||"",Bs=-1)":-1n||m[a]!==p[n]){var T=` +`+m[a].replace(" at new "," at ");return e.displayName&&T.includes("")&&(T=T.replace("",e.displayName)),T}while(1<=a&&0<=n);break}}}finally{ai=!1,Error.prepareStackTrace=l}return(l=e?e.displayName||e.name:"")?Tl(l):""}function Am(e,t){switch(e.tag){case 26:case 27:case 5:return Tl(e.type);case 16:return Tl("Lazy");case 13:return e.child!==t&&t!==null?Tl("Suspense Fallback"):Tl("Suspense");case 19:return Tl("SuspenseList");case 0:case 15:return ni(e.type,!1);case 11:return ni(e.type.render,!1);case 1:return ni(e.type,!0);case 31:return Tl("Activity");default:return""}}function Gs(e){try{var t="",l=null;do t+=Am(e,l),l=e,e=e.return;while(e);return t}catch(a){return` +Error generating stack: `+a.message+` +`+a.stack}}var ui=Object.prototype.hasOwnProperty,ii=f.unstable_scheduleCallback,ci=f.unstable_cancelCallback,jm=f.unstable_shouldYield,zm=f.unstable_requestPaint,rt=f.unstable_now,Nm=f.unstable_getCurrentPriorityLevel,Ls=f.unstable_ImmediatePriority,Ys=f.unstable_UserBlockingPriority,Cn=f.unstable_NormalPriority,Tm=f.unstable_LowPriority,Qs=f.unstable_IdlePriority,xm=f.log,Om=f.unstable_setDisableYieldValue,Ca=null,ot=null;function ll(e){if(typeof xm=="function"&&Om(e),ot&&typeof ot.setStrictMode=="function")try{ot.setStrictMode(Ca,e)}catch{}}var dt=Math.clz32?Math.clz32:Um,Dm=Math.log,Mm=Math.LN2;function Um(e){return e>>>=0,e===0?32:31-(Dm(e)/Mm|0)|0}var Rn=256,qn=262144,Hn=4194304;function xl(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Bn(e,t,l){var a=e.pendingLanes;if(a===0)return 0;var n=0,u=e.suspendedLanes,i=e.pingedLanes;e=e.warmLanes;var s=a&134217727;return s!==0?(a=s&~u,a!==0?n=xl(a):(i&=s,i!==0?n=xl(i):l||(l=s&~e,l!==0&&(n=xl(l))))):(s=a&~u,s!==0?n=xl(s):i!==0?n=xl(i):l||(l=a&~e,l!==0&&(n=xl(l)))),n===0?0:t!==0&&t!==n&&(t&u)===0&&(u=n&-n,l=t&-t,u>=l||u===32&&(l&4194048)!==0)?t:n}function Ra(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function Cm(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Xs(){var e=Hn;return Hn<<=1,(Hn&62914560)===0&&(Hn=4194304),e}function si(e){for(var t=[],l=0;31>l;l++)t.push(e);return t}function qa(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function Rm(e,t,l,a,n,u){var i=e.pendingLanes;e.pendingLanes=l,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=l,e.entangledLanes&=l,e.errorRecoveryDisabledLanes&=l,e.shellSuspendCounter=0;var s=e.entanglements,m=e.expirationTimes,p=e.hiddenUpdates;for(l=i&~l;0"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var Ym=/[\n"\\]/g;function _t(e){return e.replace(Ym,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function hi(e,t,l,a,n,u,i,s){e.name="",i!=null&&typeof i!="function"&&typeof i!="symbol"&&typeof i!="boolean"?e.type=i:e.removeAttribute("type"),t!=null?i==="number"?(t===0&&e.value===""||e.value!=t)&&(e.value=""+St(t)):e.value!==""+St(t)&&(e.value=""+St(t)):i!=="submit"&&i!=="reset"||e.removeAttribute("value"),t!=null?yi(e,i,St(t)):l!=null?yi(e,i,St(l)):a!=null&&e.removeAttribute("value"),n==null&&u!=null&&(e.defaultChecked=!!u),n!=null&&(e.checked=n&&typeof n!="function"&&typeof n!="symbol"),s!=null&&typeof s!="function"&&typeof s!="symbol"&&typeof s!="boolean"?e.name=""+St(s):e.removeAttribute("name")}function tf(e,t,l,a,n,u,i,s){if(u!=null&&typeof u!="function"&&typeof u!="symbol"&&typeof u!="boolean"&&(e.type=u),t!=null||l!=null){if(!(u!=="submit"&&u!=="reset"||t!=null)){mi(e);return}l=l!=null?""+St(l):"",t=t!=null?""+St(t):l,s||t===e.value||(e.value=t),e.defaultValue=t}a=a??n,a=typeof a!="function"&&typeof a!="symbol"&&!!a,e.checked=s?e.checked:!!a,e.defaultChecked=!!a,i!=null&&typeof i!="function"&&typeof i!="symbol"&&typeof i!="boolean"&&(e.name=i),mi(e)}function yi(e,t,l){t==="number"&&Yn(e.ownerDocument)===e||e.defaultValue===""+l||(e.defaultValue=""+l)}function Pl(e,t,l,a){if(e=e.options,t){t={};for(var n=0;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Si=!1;if(Lt)try{var La={};Object.defineProperty(La,"passive",{get:function(){Si=!0}}),window.addEventListener("test",La,La),window.removeEventListener("test",La,La)}catch{Si=!1}var nl=null,_i=null,Xn=null;function ff(){if(Xn)return Xn;var e,t=_i,l=t.length,a,n="value"in nl?nl.value:nl.textContent,u=n.length;for(e=0;e=Xa),yf=" ",vf=!1;function gf(e,t){switch(e){case"keyup":return hh.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function bf(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var aa=!1;function vh(e,t){switch(e){case"compositionend":return bf(t);case"keypress":return t.which!==32?null:(vf=!0,yf);case"textInput":return e=t.data,e===yf&&vf?null:e;default:return null}}function gh(e,t){if(aa)return e==="compositionend"||!Ni&&gf(e,t)?(e=ff(),Xn=_i=nl=null,aa=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:l,offset:t-e};e=a}e:{for(;l;){if(l.nextSibling){l=l.nextSibling;break e}l=l.parentNode}l=void 0}l=Nf(l)}}function xf(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?xf(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Of(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Yn(e.document);t instanceof e.HTMLIFrameElement;){try{var l=typeof t.contentWindow.location.href=="string"}catch{l=!1}if(l)e=t.contentWindow;else break;t=Yn(e.document)}return t}function Oi(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}var zh=Lt&&"documentMode"in document&&11>=document.documentMode,na=null,Di=null,Ka=null,Mi=!1;function Df(e,t,l){var a=l.window===l?l.document:l.nodeType===9?l:l.ownerDocument;Mi||na==null||na!==Yn(a)||(a=na,"selectionStart"in a&&Oi(a)?a={start:a.selectionStart,end:a.selectionEnd}:(a=(a.ownerDocument&&a.ownerDocument.defaultView||window).getSelection(),a={anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),Ka&&Va(Ka,a)||(Ka=a,a=qu(Di,"onSelect"),0>=i,n-=i,Ct=1<<32-dt(t)+n|l<le?(re=X,X=null):re=X.sibling;var me=_(y,X,b[le],O);if(me===null){X===null&&(X=re);break}e&&X&&me.alternate===null&&t(y,X),h=u(me,h,le),de===null?K=me:de.sibling=me,de=me,X=re}if(le===b.length)return l(y,X),oe&&Qt(y,le),K;if(X===null){for(;lele?(re=X,X=null):re=X.sibling;var Nl=_(y,X,me.value,O);if(Nl===null){X===null&&(X=re);break}e&&X&&Nl.alternate===null&&t(y,X),h=u(Nl,h,le),de===null?K=Nl:de.sibling=Nl,de=Nl,X=re}if(me.done)return l(y,X),oe&&Qt(y,le),K;if(X===null){for(;!me.done;le++,me=b.next())me=D(y,me.value,O),me!==null&&(h=u(me,h,le),de===null?K=me:de.sibling=me,de=me);return oe&&Qt(y,le),K}for(X=a(X);!me.done;le++,me=b.next())me=A(X,y,le,me.value,O),me!==null&&(e&&me.alternate!==null&&X.delete(me.key===null?le:me.key),h=u(me,h,le),de===null?K=me:de.sibling=me,de=me);return e&&X.forEach(function(Vy){return t(y,Vy)}),oe&&Qt(y,le),K}function Ee(y,h,b,O){if(typeof b=="object"&&b!==null&&b.type===ee&&b.key===null&&(b=b.props.children),typeof b=="object"&&b!==null){switch(b.$$typeof){case ye:e:{for(var K=b.key;h!==null;){if(h.key===K){if(K=b.type,K===ee){if(h.tag===7){l(y,h.sibling),O=n(h,b.props.children),O.return=y,y=O;break e}}else if(h.elementType===K||typeof K=="object"&&K!==null&&K.$$typeof===ne&&Ll(K)===h.type){l(y,h.sibling),O=n(h,b.props),Ia(O,b),O.return=y,y=O;break e}l(y,h);break}else t(y,h);h=h.sibling}b.type===ee?(O=Rl(b.props.children,y.mode,O,b.key),O.return=y,y=O):(O=In(b.type,b.key,b.props,null,y.mode,O),Ia(O,b),O.return=y,y=O)}return i(y);case Z:e:{for(K=b.key;h!==null;){if(h.key===K)if(h.tag===4&&h.stateNode.containerInfo===b.containerInfo&&h.stateNode.implementation===b.implementation){l(y,h.sibling),O=n(h,b.children||[]),O.return=y,y=O;break e}else{l(y,h);break}else t(y,h);h=h.sibling}O=Gi(b,y.mode,O),O.return=y,y=O}return i(y);case ne:return b=Ll(b),Ee(y,h,b,O)}if(I(b))return Y(y,h,b,O);if(De(b)){if(K=De(b),typeof K!="function")throw Error(r(150));return b=K.call(b),$(y,h,b,O)}if(typeof b.then=="function")return Ee(y,h,uu(b),O);if(b.$$typeof===Te)return Ee(y,h,tu(y,b),O);iu(y,b)}return typeof b=="string"&&b!==""||typeof b=="number"||typeof b=="bigint"?(b=""+b,h!==null&&h.tag===6?(l(y,h.sibling),O=n(h,b),O.return=y,y=O):(l(y,h),O=Bi(b,y.mode,O),O.return=y,y=O),i(y)):l(y,h)}return function(y,h,b,O){try{Fa=0;var K=Ee(y,h,b,O);return ya=null,K}catch(X){if(X===ha||X===au)throw X;var de=ht(29,X,null,y.mode);return de.lanes=O,de.return=y,de}finally{}}}var Ql=Pf(!0),er=Pf(!1),fl=!1;function Wi(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Fi(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function rl(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function ol(e,t,l){var a=e.updateQueue;if(a===null)return null;if(a=a.shared,(he&2)!==0){var n=a.pending;return n===null?t.next=t:(t.next=n.next,n.next=t),a.pending=t,t=Fn(e),Bf(e,null,l),t}return Wn(e,a,t,l),Fn(e)}function Pa(e,t,l){if(t=t.updateQueue,t!==null&&(t=t.shared,(l&4194048)!==0)){var a=t.lanes;a&=e.pendingLanes,l|=a,t.lanes=l,Zs(e,l)}}function Ii(e,t){var l=e.updateQueue,a=e.alternate;if(a!==null&&(a=a.updateQueue,l===a)){var n=null,u=null;if(l=l.firstBaseUpdate,l!==null){do{var i={lane:l.lane,tag:l.tag,payload:l.payload,callback:null,next:null};u===null?n=u=i:u=u.next=i,l=l.next}while(l!==null);u===null?n=u=t:u=u.next=t}else n=u=t;l={baseState:a.baseState,firstBaseUpdate:n,lastBaseUpdate:u,shared:a.shared,callbacks:a.callbacks},e.updateQueue=l;return}e=l.lastBaseUpdate,e===null?l.firstBaseUpdate=t:e.next=t,l.lastBaseUpdate=t}var Pi=!1;function en(){if(Pi){var e=ma;if(e!==null)throw e}}function tn(e,t,l,a){Pi=!1;var n=e.updateQueue;fl=!1;var u=n.firstBaseUpdate,i=n.lastBaseUpdate,s=n.shared.pending;if(s!==null){n.shared.pending=null;var m=s,p=m.next;m.next=null,i===null?u=p:i.next=p,i=m;var T=e.alternate;T!==null&&(T=T.updateQueue,s=T.lastBaseUpdate,s!==i&&(s===null?T.firstBaseUpdate=p:s.next=p,T.lastBaseUpdate=m))}if(u!==null){var D=n.baseState;i=0,T=p=m=null,s=u;do{var _=s.lane&-536870913,A=_!==s.lane;if(A?(fe&_)===_:(a&_)===_){_!==0&&_===da&&(Pi=!0),T!==null&&(T=T.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});e:{var Y=e,$=s;_=t;var Ee=l;switch($.tag){case 1:if(Y=$.payload,typeof Y=="function"){D=Y.call(Ee,D,_);break e}D=Y;break e;case 3:Y.flags=Y.flags&-65537|128;case 0:if(Y=$.payload,_=typeof Y=="function"?Y.call(Ee,D,_):Y,_==null)break e;D=B({},D,_);break e;case 2:fl=!0}}_=s.callback,_!==null&&(e.flags|=64,A&&(e.flags|=8192),A=n.callbacks,A===null?n.callbacks=[_]:A.push(_))}else A={lane:_,tag:s.tag,payload:s.payload,callback:s.callback,next:null},T===null?(p=T=A,m=D):T=T.next=A,i|=_;if(s=s.next,s===null){if(s=n.shared.pending,s===null)break;A=s,s=A.next,A.next=null,n.lastBaseUpdate=A,n.shared.pending=null}}while(!0);T===null&&(m=D),n.baseState=m,n.firstBaseUpdate=p,n.lastBaseUpdate=T,u===null&&(n.shared.lanes=0),vl|=i,e.lanes=i,e.memoizedState=D}}function tr(e,t){if(typeof e!="function")throw Error(r(191,e));e.call(t)}function lr(e,t){var l=e.callbacks;if(l!==null)for(e.callbacks=null,e=0;eu?u:8;var i=S.T,s={};S.T=s,bc(e,!1,t,l);try{var m=n(),p=S.S;if(p!==null&&p(s,m),m!==null&&typeof m=="object"&&typeof m.then=="function"){var T=Rh(m,a);nn(e,t,T,pt(e))}else nn(e,t,a,pt(e))}catch(D){nn(e,t,{then:function(){},status:"rejected",reason:D},pt())}finally{g.p=u,i!==null&&s.types!==null&&(i.types=s.types),S.T=i}}function Yh(){}function vc(e,t,l,a){if(e.tag!==5)throw Error(r(476));var n=Rr(e).queue;Cr(e,n,t,M,l===null?Yh:function(){return qr(e),l(a)})}function Rr(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:M,baseState:M,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Vt,lastRenderedState:M},next:null};var l={};return t.next={memoizedState:l,baseState:l,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Vt,lastRenderedState:l},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function qr(e){var t=Rr(e);t.next===null&&(t=e.alternate.memoizedState),nn(e,t.next.queue,{},pt())}function gc(){return Fe(En)}function Hr(){return Be().memoizedState}function Br(){return Be().memoizedState}function Qh(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var l=pt();e=rl(l);var a=ol(t,e,l);a!==null&&(ft(a,t,l),Pa(a,t,l)),t={cache:Ki()},e.payload=t;return}t=t.return}}function Xh(e,t,l){var a=pt();l={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null},vu(e)?Lr(t,l):(l=qi(e,t,l,a),l!==null&&(ft(l,e,a),Yr(l,t,a)))}function Gr(e,t,l){var a=pt();nn(e,t,l,a)}function nn(e,t,l,a){var n={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null};if(vu(e))Lr(t,n);else{var u=e.alternate;if(e.lanes===0&&(u===null||u.lanes===0)&&(u=t.lastRenderedReducer,u!==null))try{var i=t.lastRenderedState,s=u(i,l);if(n.hasEagerState=!0,n.eagerState=s,mt(s,i))return Wn(e,t,n,0),je===null&&$n(),!1}catch{}finally{}if(l=qi(e,t,n,a),l!==null)return ft(l,e,a),Yr(l,t,a),!0}return!1}function bc(e,t,l,a){if(a={lane:2,revertLane:Wc(),gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},vu(e)){if(t)throw Error(r(479))}else t=qi(e,l,a,2),t!==null&&ft(t,e,2)}function vu(e){var t=e.alternate;return e===te||t!==null&&t===te}function Lr(e,t){ga=fu=!0;var l=e.pending;l===null?t.next=t:(t.next=l.next,l.next=t),e.pending=t}function Yr(e,t,l){if((l&4194048)!==0){var a=t.lanes;a&=e.pendingLanes,l|=a,t.lanes=l,Zs(e,l)}}var un={readContext:Fe,use:du,useCallback:Ce,useContext:Ce,useEffect:Ce,useImperativeHandle:Ce,useLayoutEffect:Ce,useInsertionEffect:Ce,useMemo:Ce,useReducer:Ce,useRef:Ce,useState:Ce,useDebugValue:Ce,useDeferredValue:Ce,useTransition:Ce,useSyncExternalStore:Ce,useId:Ce,useHostTransitionStatus:Ce,useFormState:Ce,useActionState:Ce,useOptimistic:Ce,useMemoCache:Ce,useCacheRefresh:Ce};un.useEffectEvent=Ce;var Qr={readContext:Fe,use:du,useCallback:function(e,t){return lt().memoizedState=[e,t===void 0?null:t],e},useContext:Fe,useEffect:jr,useImperativeHandle:function(e,t,l){l=l!=null?l.concat([e]):null,hu(4194308,4,xr.bind(null,t,e),l)},useLayoutEffect:function(e,t){return hu(4194308,4,e,t)},useInsertionEffect:function(e,t){hu(4,2,e,t)},useMemo:function(e,t){var l=lt();t=t===void 0?null:t;var a=e();if(Xl){ll(!0);try{e()}finally{ll(!1)}}return l.memoizedState=[a,t],a},useReducer:function(e,t,l){var a=lt();if(l!==void 0){var n=l(t);if(Xl){ll(!0);try{l(t)}finally{ll(!1)}}}else n=t;return a.memoizedState=a.baseState=n,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:n},a.queue=e,e=e.dispatch=Xh.bind(null,te,e),[a.memoizedState,e]},useRef:function(e){var t=lt();return e={current:e},t.memoizedState=e},useState:function(e){e=oc(e);var t=e.queue,l=Gr.bind(null,te,t);return t.dispatch=l,[e.memoizedState,l]},useDebugValue:hc,useDeferredValue:function(e,t){var l=lt();return yc(l,e,t)},useTransition:function(){var e=oc(!1);return e=Cr.bind(null,te,e.queue,!0,!1),lt().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,l){var a=te,n=lt();if(oe){if(l===void 0)throw Error(r(407));l=l()}else{if(l=t(),je===null)throw Error(r(349));(fe&127)!==0||sr(a,t,l)}n.memoizedState=l;var u={value:l,getSnapshot:t};return n.queue=u,jr(rr.bind(null,a,u,e),[e]),a.flags|=2048,pa(9,{destroy:void 0},fr.bind(null,a,u,l,t),null),l},useId:function(){var e=lt(),t=je.identifierPrefix;if(oe){var l=Rt,a=Ct;l=(a&~(1<<32-dt(a)-1)).toString(32)+l,t="_"+t+"R_"+l,l=ru++,0<\/script>",u=u.removeChild(u.firstChild);break;case"select":u=typeof a.is=="string"?i.createElement("select",{is:a.is}):i.createElement("select"),a.multiple?u.multiple=!0:a.size&&(u.size=a.size);break;default:u=typeof a.is=="string"?i.createElement(n,{is:a.is}):i.createElement(n)}}u[$e]=t,u[at]=a;e:for(i=t.child;i!==null;){if(i.tag===5||i.tag===6)u.appendChild(i.stateNode);else if(i.tag!==4&&i.tag!==27&&i.child!==null){i.child.return=i,i=i.child;continue}if(i===t)break e;for(;i.sibling===null;){if(i.return===null||i.return===t)break e;i=i.return}i.sibling.return=i.return,i=i.sibling}t.stateNode=u;e:switch(Pe(u,n,a),n){case"button":case"input":case"select":case"textarea":a=!!a.autoFocus;break e;case"img":a=!0;break e;default:a=!1}a&&Jt(t)}}return Ne(t),Uc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,l),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==a&&Jt(t);else{if(typeof a!="string"&&t.stateNode===null)throw Error(r(166));if(e=k.current,ra(t)){if(e=t.stateNode,l=t.memoizedProps,a=null,n=We,n!==null)switch(n.tag){case 27:case 5:a=n.memoizedProps}e[$e]=t,e=!!(e.nodeValue===l||a!==null&&a.suppressHydrationWarning===!0||cd(e.nodeValue,l)),e||cl(t,!0)}else e=Hu(e).createTextNode(a),e[$e]=t,t.stateNode=e}return Ne(t),null;case 31:if(l=t.memoizedState,e===null||e.memoizedState!==null){if(a=ra(t),l!==null){if(e===null){if(!a)throw Error(r(318));if(e=t.memoizedState,e=e!==null?e.dehydrated:null,!e)throw Error(r(557));e[$e]=t}else ql(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Ne(t),e=!1}else l=Xi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=l),e=!0;if(!e)return t.flags&256?(vt(t),t):(vt(t),null);if((t.flags&128)!==0)throw Error(r(558))}return Ne(t),null;case 13:if(a=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(n=ra(t),a!==null&&a.dehydrated!==null){if(e===null){if(!n)throw Error(r(318));if(n=t.memoizedState,n=n!==null?n.dehydrated:null,!n)throw Error(r(317));n[$e]=t}else ql(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Ne(t),n=!1}else n=Xi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),n=!0;if(!n)return t.flags&256?(vt(t),t):(vt(t),null)}return vt(t),(t.flags&128)!==0?(t.lanes=l,t):(l=a!==null,e=e!==null&&e.memoizedState!==null,l&&(a=t.child,n=null,a.alternate!==null&&a.alternate.memoizedState!==null&&a.alternate.memoizedState.cachePool!==null&&(n=a.alternate.memoizedState.cachePool.pool),u=null,a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(u=a.memoizedState.cachePool.pool),u!==n&&(a.flags|=2048)),l!==e&&l&&(t.child.flags|=8192),_u(t,t.updateQueue),Ne(t),null);case 4:return Ae(),e===null&&es(t.stateNode.containerInfo),Ne(t),null;case 10:return wt(t.type),Ne(t),null;case 19:if(j(He),a=t.memoizedState,a===null)return Ne(t),null;if(n=(t.flags&128)!==0,u=a.rendering,u===null)if(n)sn(a,!1);else{if(Re!==0||e!==null&&(e.flags&128)!==0)for(e=t.child;e!==null;){if(u=su(e),u!==null){for(t.flags|=128,sn(a,!1),e=u.updateQueue,t.updateQueue=e,_u(t,e),t.subtreeFlags=0,e=l,l=t.child;l!==null;)Gf(l,e),l=l.sibling;return q(He,He.current&1|2),oe&&Qt(t,a.treeForkCount),t.child}e=e.sibling}a.tail!==null&&rt()>Nu&&(t.flags|=128,n=!0,sn(a,!1),t.lanes=4194304)}else{if(!n)if(e=su(u),e!==null){if(t.flags|=128,n=!0,e=e.updateQueue,t.updateQueue=e,_u(t,e),sn(a,!0),a.tail===null&&a.tailMode==="hidden"&&!u.alternate&&!oe)return Ne(t),null}else 2*rt()-a.renderingStartTime>Nu&&l!==536870912&&(t.flags|=128,n=!0,sn(a,!1),t.lanes=4194304);a.isBackwards?(u.sibling=t.child,t.child=u):(e=a.last,e!==null?e.sibling=u:t.child=u,a.last=u)}return a.tail!==null?(e=a.tail,a.rendering=e,a.tail=e.sibling,a.renderingStartTime=rt(),e.sibling=null,l=He.current,q(He,n?l&1|2:l&1),oe&&Qt(t,a.treeForkCount),e):(Ne(t),null);case 22:case 23:return vt(t),tc(),a=t.memoizedState!==null,e!==null?e.memoizedState!==null!==a&&(t.flags|=8192):a&&(t.flags|=8192),a?(l&536870912)!==0&&(t.flags&128)===0&&(Ne(t),t.subtreeFlags&6&&(t.flags|=8192)):Ne(t),l=t.updateQueue,l!==null&&_u(t,l.retryQueue),l=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(l=e.memoizedState.cachePool.pool),a=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(a=t.memoizedState.cachePool.pool),a!==l&&(t.flags|=2048),e!==null&&j(Gl),null;case 24:return l=null,e!==null&&(l=e.memoizedState.cache),t.memoizedState.cache!==l&&(t.flags|=2048),wt(Ge),Ne(t),null;case 25:return null;case 30:return null}throw Error(r(156,t.tag))}function Jh(e,t){switch(Yi(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return wt(Ge),Ae(),e=t.flags,(e&65536)!==0&&(e&128)===0?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return tl(t),null;case 31:if(t.memoizedState!==null){if(vt(t),t.alternate===null)throw Error(r(340));ql()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(vt(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(r(340));ql()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return j(He),null;case 4:return Ae(),null;case 10:return wt(t.type),null;case 22:case 23:return vt(t),tc(),e!==null&&j(Gl),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return wt(Ge),null;case 25:return null;default:return null}}function oo(e,t){switch(Yi(t),t.tag){case 3:wt(Ge),Ae();break;case 26:case 27:case 5:tl(t);break;case 4:Ae();break;case 31:t.memoizedState!==null&&vt(t);break;case 13:vt(t);break;case 19:j(He);break;case 10:wt(t.type);break;case 22:case 23:vt(t),tc(),e!==null&&j(Gl);break;case 24:wt(Ge)}}function fn(e,t){try{var l=t.updateQueue,a=l!==null?l.lastEffect:null;if(a!==null){var n=a.next;l=n;do{if((l.tag&e)===e){a=void 0;var u=l.create,i=l.inst;a=u(),i.destroy=a}l=l.next}while(l!==n)}}catch(s){pe(t,t.return,s)}}function hl(e,t,l){try{var a=t.updateQueue,n=a!==null?a.lastEffect:null;if(n!==null){var u=n.next;a=u;do{if((a.tag&e)===e){var i=a.inst,s=i.destroy;if(s!==void 0){i.destroy=void 0,n=t;var m=l,p=s;try{p()}catch(T){pe(n,m,T)}}}a=a.next}while(a!==u)}}catch(T){pe(t,t.return,T)}}function mo(e){var t=e.updateQueue;if(t!==null){var l=e.stateNode;try{lr(t,l)}catch(a){pe(e,e.return,a)}}}function ho(e,t,l){l.props=wl(e.type,e.memoizedProps),l.state=e.memoizedState;try{l.componentWillUnmount()}catch(a){pe(e,t,a)}}function rn(e,t){try{var l=e.ref;if(l!==null){switch(e.tag){case 26:case 27:case 5:var a=e.stateNode;break;case 30:a=e.stateNode;break;default:a=e.stateNode}typeof l=="function"?e.refCleanup=l(a):l.current=a}}catch(n){pe(e,t,n)}}function qt(e,t){var l=e.ref,a=e.refCleanup;if(l!==null)if(typeof a=="function")try{a()}catch(n){pe(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof l=="function")try{l(null)}catch(n){pe(e,t,n)}else l.current=null}function yo(e){var t=e.type,l=e.memoizedProps,a=e.stateNode;try{e:switch(t){case"button":case"input":case"select":case"textarea":l.autoFocus&&a.focus();break e;case"img":l.src?a.src=l.src:l.srcSet&&(a.srcset=l.srcSet)}}catch(n){pe(e,e.return,n)}}function Cc(e,t,l){try{var a=e.stateNode;yy(a,e.type,l,t),a[at]=t}catch(n){pe(e,e.return,n)}}function vo(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&_l(e.type)||e.tag===4}function Rc(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||vo(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&_l(e.type)||e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function qc(e,t,l){var a=e.tag;if(a===5||a===6)e=e.stateNode,t?(l.nodeType===9?l.body:l.nodeName==="HTML"?l.ownerDocument.body:l).insertBefore(e,t):(t=l.nodeType===9?l.body:l.nodeName==="HTML"?l.ownerDocument.body:l,t.appendChild(e),l=l._reactRootContainer,l!=null||t.onclick!==null||(t.onclick=Gt));else if(a!==4&&(a===27&&_l(e.type)&&(l=e.stateNode,t=null),e=e.child,e!==null))for(qc(e,t,l),e=e.sibling;e!==null;)qc(e,t,l),e=e.sibling}function Eu(e,t,l){var a=e.tag;if(a===5||a===6)e=e.stateNode,t?l.insertBefore(e,t):l.appendChild(e);else if(a!==4&&(a===27&&_l(e.type)&&(l=e.stateNode),e=e.child,e!==null))for(Eu(e,t,l),e=e.sibling;e!==null;)Eu(e,t,l),e=e.sibling}function go(e){var t=e.stateNode,l=e.memoizedProps;try{for(var a=e.type,n=t.attributes;n.length;)t.removeAttributeNode(n[0]);Pe(t,a,l),t[$e]=e,t[at]=l}catch(u){pe(e,e.return,u)}}var kt=!1,Qe=!1,Hc=!1,bo=typeof WeakSet=="function"?WeakSet:Set,Ve=null;function kh(e,t){if(e=e.containerInfo,as=wu,e=Of(e),Oi(e)){if("selectionStart"in e)var l={start:e.selectionStart,end:e.selectionEnd};else e:{l=(l=e.ownerDocument)&&l.defaultView||window;var a=l.getSelection&&l.getSelection();if(a&&a.rangeCount!==0){l=a.anchorNode;var n=a.anchorOffset,u=a.focusNode;a=a.focusOffset;try{l.nodeType,u.nodeType}catch{l=null;break e}var i=0,s=-1,m=-1,p=0,T=0,D=e,_=null;t:for(;;){for(var A;D!==l||n!==0&&D.nodeType!==3||(s=i+n),D!==u||a!==0&&D.nodeType!==3||(m=i+a),D.nodeType===3&&(i+=D.nodeValue.length),(A=D.firstChild)!==null;)_=D,D=A;for(;;){if(D===e)break t;if(_===l&&++p===n&&(s=i),_===u&&++T===a&&(m=i),(A=D.nextSibling)!==null)break;D=_,_=D.parentNode}D=A}l=s===-1||m===-1?null:{start:s,end:m}}else l=null}l=l||{start:0,end:0}}else l=null;for(ns={focusedElem:e,selectionRange:l},wu=!1,Ve=t;Ve!==null;)if(t=Ve,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,Ve=e;else for(;Ve!==null;){switch(t=Ve,u=t.alternate,e=t.flags,t.tag){case 0:if((e&4)!==0&&(e=t.updateQueue,e=e!==null?e.events:null,e!==null))for(l=0;l title"))),Pe(u,a,l),u[$e]=e,Ze(u),a=u;break e;case"link":var i=jd("link","href",n).get(a+(l.href||""));if(i){for(var s=0;sEe&&(i=Ee,Ee=$,$=i);var y=Tf(s,$),h=Tf(s,Ee);if(y&&h&&(A.rangeCount!==1||A.anchorNode!==y.node||A.anchorOffset!==y.offset||A.focusNode!==h.node||A.focusOffset!==h.offset)){var b=D.createRange();b.setStart(y.node,y.offset),A.removeAllRanges(),$>Ee?(A.addRange(b),A.extend(h.node,h.offset)):(b.setEnd(h.node,h.offset),A.addRange(b))}}}}for(D=[],A=s;A=A.parentNode;)A.nodeType===1&&D.push({element:A,left:A.scrollLeft,top:A.scrollTop});for(typeof s.focus=="function"&&s.focus(),s=0;sl?32:l,S.T=null,l=wc,wc=null;var u=bl,i=Pt;if(we=0,ja=bl=null,Pt=0,(he&6)!==0)throw Error(r(331));var s=he;if(he|=4,Oo(u.current),No(u,u.current,i,l),he=s,vn(0,!1),ot&&typeof ot.onPostCommitFiberRoot=="function")try{ot.onPostCommitFiberRoot(Ca,u)}catch{}return!0}finally{g.p=n,S.T=a,Jo(e,t)}}function $o(e,t,l){t=At(l,t),t=Ec(e.stateNode,t,2),e=ol(e,t,2),e!==null&&(qa(e,2),Ht(e))}function pe(e,t,l){if(e.tag===3)$o(e,e,l);else for(;t!==null;){if(t.tag===3){$o(t,e,l);break}else if(t.tag===1){var a=t.stateNode;if(typeof t.type.getDerivedStateFromError=="function"||typeof a.componentDidCatch=="function"&&(gl===null||!gl.has(a))){e=At(l,e),l=$r(2),a=ol(t,l,2),a!==null&&(Wr(l,a,t,e),qa(a,2),Ht(a));break}}t=t.return}}function Jc(e,t,l){var a=e.pingCache;if(a===null){a=e.pingCache=new Fh;var n=new Set;a.set(t,n)}else n=a.get(t),n===void 0&&(n=new Set,a.set(t,n));n.has(l)||(Lc=!0,n.add(l),e=ly.bind(null,e,t,l),t.then(e,e))}function ly(e,t,l){var a=e.pingCache;a!==null&&a.delete(t),e.pingedLanes|=e.suspendedLanes&l,e.warmLanes&=~l,je===e&&(fe&l)===l&&(Re===4||Re===3&&(fe&62914560)===fe&&300>rt()-zu?(he&2)===0&&za(e,0):Yc|=l,Aa===fe&&(Aa=0)),Ht(e)}function Wo(e,t){t===0&&(t=Xs()),e=Cl(e,t),e!==null&&(qa(e,t),Ht(e))}function ay(e){var t=e.memoizedState,l=0;t!==null&&(l=t.retryLane),Wo(e,l)}function ny(e,t){var l=0;switch(e.tag){case 31:case 13:var a=e.stateNode,n=e.memoizedState;n!==null&&(l=n.retryLane);break;case 19:a=e.stateNode;break;case 22:a=e.stateNode._retryCache;break;default:throw Error(r(314))}a!==null&&a.delete(t),Wo(e,l)}function uy(e,t){return ii(e,t)}var Uu=null,Ta=null,kc=!1,Cu=!1,$c=!1,Sl=0;function Ht(e){e!==Ta&&e.next===null&&(Ta===null?Uu=Ta=e:Ta=Ta.next=e),Cu=!0,kc||(kc=!0,cy())}function vn(e,t){if(!$c&&Cu){$c=!0;do for(var l=!1,a=Uu;a!==null;){if(e!==0){var n=a.pendingLanes;if(n===0)var u=0;else{var i=a.suspendedLanes,s=a.pingedLanes;u=(1<<31-dt(42|e)+1)-1,u&=n&~(i&~s),u=u&201326741?u&201326741|1:u?u|2:0}u!==0&&(l=!0,ed(a,u))}else u=fe,u=Bn(a,a===je?u:0,a.cancelPendingCommit!==null||a.timeoutHandle!==-1),(u&3)===0||Ra(a,u)||(l=!0,ed(a,u));a=a.next}while(l);$c=!1}}function iy(){Fo()}function Fo(){Cu=kc=!1;var e=0;Sl!==0&&gy()&&(e=Sl);for(var t=rt(),l=null,a=Uu;a!==null;){var n=a.next,u=Io(a,t);u===0?(a.next=null,l===null?Uu=n:l.next=n,n===null&&(Ta=l)):(l=a,(e!==0||(u&3)!==0)&&(Cu=!0)),a=n}we!==0&&we!==5||vn(e),Sl!==0&&(Sl=0)}function Io(e,t){for(var l=e.suspendedLanes,a=e.pingedLanes,n=e.expirationTimes,u=e.pendingLanes&-62914561;0s)break;var T=m.transferSize,D=m.initiatorType;T&&sd(D)&&(m=m.responseEnd,i+=T*(m"u"?null:document;function Sd(e,t,l){var a=xa;if(a&&typeof t=="string"&&t){var n=_t(t);n='link[rel="'+e+'"][href="'+n+'"]',typeof l=="string"&&(n+='[crossorigin="'+l+'"]'),pd.has(n)||(pd.add(n),e={rel:e,crossOrigin:l,href:t},a.querySelector(n)===null&&(t=a.createElement("link"),Pe(t,"link",e),Ze(t),a.head.appendChild(t)))}}function Ny(e){el.D(e),Sd("dns-prefetch",e,null)}function Ty(e,t){el.C(e,t),Sd("preconnect",e,t)}function xy(e,t,l){el.L(e,t,l);var a=xa;if(a&&e&&t){var n='link[rel="preload"][as="'+_t(t)+'"]';t==="image"&&l&&l.imageSrcSet?(n+='[imagesrcset="'+_t(l.imageSrcSet)+'"]',typeof l.imageSizes=="string"&&(n+='[imagesizes="'+_t(l.imageSizes)+'"]')):n+='[href="'+_t(e)+'"]';var u=n;switch(t){case"style":u=Oa(e);break;case"script":u=Da(e)}Ot.has(u)||(e=B({rel:"preload",href:t==="image"&&l&&l.imageSrcSet?void 0:e,as:t},l),Ot.set(u,e),a.querySelector(n)!==null||t==="style"&&a.querySelector(Sn(u))||t==="script"&&a.querySelector(_n(u))||(t=a.createElement("link"),Pe(t,"link",e),Ze(t),a.head.appendChild(t)))}}function Oy(e,t){el.m(e,t);var l=xa;if(l&&e){var a=t&&typeof t.as=="string"?t.as:"script",n='link[rel="modulepreload"][as="'+_t(a)+'"][href="'+_t(e)+'"]',u=n;switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":u=Da(e)}if(!Ot.has(u)&&(e=B({rel:"modulepreload",href:e},t),Ot.set(u,e),l.querySelector(n)===null)){switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(l.querySelector(_n(u)))return}a=l.createElement("link"),Pe(a,"link",e),Ze(a),l.head.appendChild(a)}}}function Dy(e,t,l){el.S(e,t,l);var a=xa;if(a&&e){var n=Fl(a).hoistableStyles,u=Oa(e);t=t||"default";var i=n.get(u);if(!i){var s={loading:0,preload:null};if(i=a.querySelector(Sn(u)))s.loading=5;else{e=B({rel:"stylesheet",href:e,"data-precedence":t},l),(l=Ot.get(u))&&os(e,l);var m=i=a.createElement("link");Ze(m),Pe(m,"link",e),m._p=new Promise(function(p,T){m.onload=p,m.onerror=T}),m.addEventListener("load",function(){s.loading|=1}),m.addEventListener("error",function(){s.loading|=2}),s.loading|=4,Gu(i,t,a)}i={type:"stylesheet",instance:i,count:1,state:s},n.set(u,i)}}}function My(e,t){el.X(e,t);var l=xa;if(l&&e){var a=Fl(l).hoistableScripts,n=Da(e),u=a.get(n);u||(u=l.querySelector(_n(n)),u||(e=B({src:e,async:!0},t),(t=Ot.get(n))&&ds(e,t),u=l.createElement("script"),Ze(u),Pe(u,"link",e),l.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},a.set(n,u))}}function Uy(e,t){el.M(e,t);var l=xa;if(l&&e){var a=Fl(l).hoistableScripts,n=Da(e),u=a.get(n);u||(u=l.querySelector(_n(n)),u||(e=B({src:e,async:!0,type:"module"},t),(t=Ot.get(n))&&ds(e,t),u=l.createElement("script"),Ze(u),Pe(u,"link",e),l.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},a.set(n,u))}}function _d(e,t,l,a){var n=(n=k.current)?Bu(n):null;if(!n)throw Error(r(446));switch(e){case"meta":case"title":return null;case"style":return typeof l.precedence=="string"&&typeof l.href=="string"?(t=Oa(l.href),l=Fl(n).hoistableStyles,a=l.get(t),a||(a={type:"style",instance:null,count:0,state:null},l.set(t,a)),a):{type:"void",instance:null,count:0,state:null};case"link":if(l.rel==="stylesheet"&&typeof l.href=="string"&&typeof l.precedence=="string"){e=Oa(l.href);var u=Fl(n).hoistableStyles,i=u.get(e);if(i||(n=n.ownerDocument||n,i={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},u.set(e,i),(u=n.querySelector(Sn(e)))&&!u._p&&(i.instance=u,i.state.loading=5),Ot.has(e)||(l={rel:"preload",as:"style",href:l.href,crossOrigin:l.crossOrigin,integrity:l.integrity,media:l.media,hrefLang:l.hrefLang,referrerPolicy:l.referrerPolicy},Ot.set(e,l),u||Cy(n,e,l,i.state))),t&&a===null)throw Error(r(528,""));return i}if(t&&a!==null)throw Error(r(529,""));return null;case"script":return t=l.async,l=l.src,typeof l=="string"&&t&&typeof t!="function"&&typeof t!="symbol"?(t=Da(l),l=Fl(n).hoistableScripts,a=l.get(t),a||(a={type:"script",instance:null,count:0,state:null},l.set(t,a)),a):{type:"void",instance:null,count:0,state:null};default:throw Error(r(444,e))}}function Oa(e){return'href="'+_t(e)+'"'}function Sn(e){return'link[rel="stylesheet"]['+e+"]"}function Ed(e){return B({},e,{"data-precedence":e.precedence,precedence:null})}function Cy(e,t,l,a){e.querySelector('link[rel="preload"][as="style"]['+t+"]")?a.loading=1:(t=e.createElement("link"),a.preload=t,t.addEventListener("load",function(){return a.loading|=1}),t.addEventListener("error",function(){return a.loading|=2}),Pe(t,"link",l),Ze(t),e.head.appendChild(t))}function Da(e){return'[src="'+_t(e)+'"]'}function _n(e){return"script[async]"+e}function Ad(e,t,l){if(t.count++,t.instance===null)switch(t.type){case"style":var a=e.querySelector('style[data-href~="'+_t(l.href)+'"]');if(a)return t.instance=a,Ze(a),a;var n=B({},l,{"data-href":l.href,"data-precedence":l.precedence,href:null,precedence:null});return a=(e.ownerDocument||e).createElement("style"),Ze(a),Pe(a,"style",n),Gu(a,l.precedence,e),t.instance=a;case"stylesheet":n=Oa(l.href);var u=e.querySelector(Sn(n));if(u)return t.state.loading|=4,t.instance=u,Ze(u),u;a=Ed(l),(n=Ot.get(n))&&os(a,n),u=(e.ownerDocument||e).createElement("link"),Ze(u);var i=u;return i._p=new Promise(function(s,m){i.onload=s,i.onerror=m}),Pe(u,"link",a),t.state.loading|=4,Gu(u,l.precedence,e),t.instance=u;case"script":return u=Da(l.src),(n=e.querySelector(_n(u)))?(t.instance=n,Ze(n),n):(a=l,(n=Ot.get(u))&&(a=B({},l),ds(a,n)),e=e.ownerDocument||e,n=e.createElement("script"),Ze(n),Pe(n,"link",a),e.head.appendChild(n),t.instance=n);case"void":return null;default:throw Error(r(443,t.type))}else t.type==="stylesheet"&&(t.state.loading&4)===0&&(a=t.instance,t.state.loading|=4,Gu(a,l.precedence,e));return t.instance}function Gu(e,t,l){for(var a=l.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),n=a.length?a[a.length-1]:null,u=n,i=0;i title"):null)}function Ry(e,t,l){if(l===1||t.itemProp!=null)return!1;switch(e){case"meta":case"title":return!0;case"style":if(typeof t.precedence!="string"||typeof t.href!="string"||t.href==="")break;return!0;case"link":if(typeof t.rel!="string"||typeof t.href!="string"||t.href===""||t.onLoad||t.onError)break;switch(t.rel){case"stylesheet":return e=t.disabled,typeof t.precedence=="string"&&e==null;default:return!0}case"script":if(t.async&&typeof t.async!="function"&&typeof t.async!="symbol"&&!t.onLoad&&!t.onError&&t.src&&typeof t.src=="string")return!0}return!1}function Nd(e){return!(e.type==="stylesheet"&&(e.state.loading&3)===0)}function qy(e,t,l,a){if(l.type==="stylesheet"&&(typeof a.media!="string"||matchMedia(a.media).matches!==!1)&&(l.state.loading&4)===0){if(l.instance===null){var n=Oa(a.href),u=t.querySelector(Sn(n));if(u){t=u._p,t!==null&&typeof t=="object"&&typeof t.then=="function"&&(e.count++,e=Yu.bind(e),t.then(e,e)),l.state.loading|=4,l.instance=u,Ze(u);return}u=t.ownerDocument||t,a=Ed(a),(n=Ot.get(n))&&os(a,n),u=u.createElement("link"),Ze(u);var i=u;i._p=new Promise(function(s,m){i.onload=s,i.onerror=m}),Pe(u,"link",a),l.instance=u}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(l,t),(t=l.state.preload)&&(l.state.loading&3)===0&&(e.count++,l=Yu.bind(e),t.addEventListener("load",l),t.addEventListener("error",l))}}var ms=0;function Hy(e,t){return e.stylesheets&&e.count===0&&Xu(e,e.stylesheets),0ms?50:800)+t);return e.unsuspend=l,function(){e.unsuspend=null,clearTimeout(a),clearTimeout(n)}}:null}function Yu(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Xu(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Qu=null;function Xu(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Qu=new Map,t.forEach(By,e),Qu=null,Yu.call(e))}function By(e,t){if(!(t.state.loading&4)){var l=Qu.get(e);if(l)var a=l.get(null);else{l=new Map,Qu.set(e,l);for(var n=e.querySelectorAll("link[data-precedence],style[data-precedence]"),u=0;u"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(f)}catch(d){console.error(d)}}return f(),Es.exports=Py(),Es.exports}var tv=ev();/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const um=(...f)=>f.filter((d,v,r)=>!!d&&d.trim()!==""&&r.indexOf(d)===v).join(" ").trim();/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const lv=f=>f.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase();/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const av=f=>f.replace(/^([A-Z])|[\s-_]+(\w)/g,(d,v,r)=>r?r.toUpperCase():v.toLowerCase());/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Wd=f=>{const d=av(f);return d.charAt(0).toUpperCase()+d.slice(1)};/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */var nv={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const uv=f=>{for(const d in f)if(d.startsWith("aria-")||d==="role"||d==="title")return!0;return!1};/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const iv=H.forwardRef(({color:f="currentColor",size:d=24,strokeWidth:v=2,absoluteStrokeWidth:r,className:x="",children:N,iconNode:C,...w},U)=>H.createElement("svg",{ref:U,...nv,width:d,height:d,stroke:f,strokeWidth:r?Number(v)*24/Number(d):v,className:um("lucide",x),...!N&&!uv(w)&&{"aria-hidden":"true"},...w},[...C.map(([E,G])=>H.createElement(E,G)),...Array.isArray(N)?N:[N]]));/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Ke=(f,d)=>{const v=H.forwardRef(({className:r,...x},N)=>H.createElement(iv,{ref:N,iconNode:d,className:um(`lucide-${lv(Wd(f))}`,`lucide-${f}`,r),...x}));return v.displayName=Wd(f),v};/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const cv=[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]],sv=Ke("arrow-left",cv);/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const fv=[["path",{d:"M7 7h10v10",key:"1tivn9"}],["path",{d:"M7 17 17 7",key:"1vkiza"}]],Mn=Ke("arrow-up-right",fv);/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const rv=[["path",{d:"m15 18-6-6 6-6",key:"1wnfg3"}]],ov=Ke("chevron-left",rv);/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const dv=[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]],im=Ke("chevron-right",dv);/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const mv=[["path",{d:"M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8",key:"5wwlr5"}],["path",{d:"M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z",key:"r6nss1"}]],cm=Ke("house",mv);/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const hv=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",ry:"2",key:"1m3agn"}],["circle",{cx:"9",cy:"9",r:"2",key:"af1f0g"}],["path",{d:"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21",key:"1xmnt7"}]],yv=Ke("image",hv);/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const vv=[["path",{d:"M9 17H7A5 5 0 0 1 7 7h2",key:"8i5ue5"}],["path",{d:"M15 7h2a5 5 0 1 1 0 10h-2",key:"1b9ql8"}],["line",{x1:"8",x2:"16",y1:"12",y2:"12",key:"1jonct"}]],sm=Ke("link-2",vv);/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const gv=[["circle",{cx:"12",cy:"16",r:"1",key:"1au0dj"}],["rect",{x:"3",y:"10",width:"18",height:"12",rx:"2",key:"6s8ecr"}],["path",{d:"M7 10V7a5 5 0 0 1 10 0v3",key:"1pqi11"}]],bv=Ke("lock-keyhole",gv);/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const pv=[["path",{d:"m16 17 5-5-5-5",key:"1bji2h"}],["path",{d:"M21 12H9",key:"dn1m92"}],["path",{d:"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4",key:"1uf3rs"}]],fm=Ke("log-out",pv);/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Sv=[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]],Fd=Ke("plus",Sv);/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const _v=[["path",{d:"M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z",key:"1c8476"}],["path",{d:"M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7",key:"1ydtos"}],["path",{d:"M7 3v4a1 1 0 0 0 1 1h7",key:"t51u73"}]],Ev=Ke("save",_v);/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Av=[["path",{d:"m21 21-4.34-4.34",key:"14j7rj"}],["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}]],rm=Ke("search",Av);/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const jv=[["path",{d:"M10 5H3",key:"1qgfaw"}],["path",{d:"M12 19H3",key:"yhmn1j"}],["path",{d:"M14 3v4",key:"1sua03"}],["path",{d:"M16 17v4",key:"1q0r14"}],["path",{d:"M21 12h-9",key:"1o4lsq"}],["path",{d:"M21 19h-5",key:"1rlt1p"}],["path",{d:"M21 5h-7",key:"1oszz2"}],["path",{d:"M8 10v4",key:"tgpxqk"}],["path",{d:"M8 12H3",key:"a7s4jb"}]],om=Ke("sliders-horizontal",jv);/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const zv=[["path",{d:"M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z",key:"1s2grr"}],["path",{d:"M20 2v4",key:"1rf3ol"}],["path",{d:"M22 4h-4",key:"gwowj6"}],["circle",{cx:"4",cy:"20",r:"2",key:"6kqj1y"}]],Nv=Ke("sparkles",zv);/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Tv=[["path",{d:"M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z",key:"r04s7s"}]],qs=Ke("star",Tv);/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const xv=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],Iu=Ke("trash-2",xv);/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Ov=[["path",{d:"M12 3v12",key:"1x0j5s"}],["path",{d:"m17 8-5-5-5 5",key:"7q97r8"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}]],Dv=Ke("upload",Ov);/** + * @license lucide-react v0.575.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Mv=[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]],Un=Ke("x",Mv),Uv="2026-07-16T12:00:00.000Z",dm=["Administración","Recursos Humanos","CDC"],Cv=[];function Rv(){return{schemaVersion:1,apps:Cv.map(f=>({...f,keywords:[...f.keywords]})),favoritesByUser:{},recentByUser:{},updatedAt:Uv}}const xn="glm-hub:state:v1";function qv(f){if(!f||typeof f!="object")return!1;const d=f;return typeof d.id=="string"&&typeof d.name=="string"&&typeof d.description=="string"&&typeof d.category=="string"&&Array.isArray(d.keywords)&&typeof d.url=="string"&&typeof d.mark=="string"&&typeof d.accent=="string"&&typeof d.iconDataUrl=="string"&&(d.visibility==="published"||d.visibility==="hidden")}function Hv(f){if(!f||typeof f!="object")return!1;const d=f;return d.schemaVersion===1&&Array.isArray(d.apps)&&d.apps.every(qv)&&!!d.favoritesByUser&&typeof d.favoritesByUser=="object"&&!!d.recentByUser&&typeof d.recentByUser=="object"}function Id(f,d){return Object.fromEntries(Object.entries(f).map(([v,r])=>[v,r.filter(x=>d.has(x))]))}function Bv(f){switch(f){case"Administración":case"Recursos Humanos":case"CDC":return f;case"Recursos":return"Recursos Humanos";case"Clientes":case"Creatividad":return"CDC";case"Operaciones":case"Analítica":default:return"Administración"}}function Gv(f){let d=!1;const v=f.apps.map(r=>{const x=Bv(r.category);return x===r.category?r:(d=!0,{...r,category:x})});return d?{...f,apps:v,updatedAt:new Date().toISOString()}:f}function Lv(f){const d=f.apps.filter(r=>!!r.iconDataUrl.trim());if(d.length===f.apps.length)return f;const v=new Set(d.map(r=>r.id));return{...f,apps:d,favoritesByUser:Id(f.favoritesByUser,v),recentByUser:Id(f.recentByUser,v),updatedAt:new Date().toISOString()}}function Yv(){const f=Rv();try{const d=localStorage.getItem(xn);if(!d)return localStorage.setItem(xn,JSON.stringify(f)),{data:f,warning:""};const v=JSON.parse(d);if(!Hv(v))return localStorage.setItem(`${xn}:backup`,d),localStorage.setItem(xn,JSON.stringify(f)),{data:f,warning:"Los datos locales no eran válidos. Restauramos el catálogo inicial."};const r=Gv(v),x=Lv(r);return x!==v&&localStorage.setItem(xn,JSON.stringify(x)),{data:x,warning:""}}catch{return{data:f,warning:"No pudimos leer los datos guardados. Estás viendo el catálogo inicial."}}}function Ua(f){return f.normalize("NFD").replace(/[\u0300-\u036f]/g,"").toLocaleLowerCase("es-DO").trim()}function Jl(f){if(!f.trim())return null;try{const d=new URL(f.trim()),v=d.protocol==="http:"&&(d.hostname==="localhost"||d.hostname==="127.0.0.1");return d.protocol!=="https:"&&!v||d.username||d.password?null:d.toString()}catch{return null}}const mm="https://dbit.digitalcompass.agency".trim(),hm="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE".trim(),ym=globalThis.supabase;if(!mm||!hm)throw new Error("Faltan VITE_SUPABASE_URL o VITE_SUPABASE_ANON_KEY en el archivo .env.");if(!ym)throw new Error("No se pudo cargar la librería de Supabase.");const qe=ym.createClient(mm,hm,{auth:{autoRefreshToken:!0,persistSession:!0,detectSessionInUrl:!0,flowType:"pkce"}}),Dn="glm_hub_apps",Ds="glm_hub_favorites",vm="glm_hub_recent_apps",Pu="glm-hub-icons",Pd="glm-hub:supabase-catalog-migrated:v1";function gm(f){return{id:f.id,name:f.name,description:f.description,category:f.category,keywords:[],url:f.url??"",mark:"",accent:"#4F758B",iconDataUrl:f.icon_url,visibility:f.visibility,createdAt:f.created_at,updatedAt:f.updated_at}}function Qv(f){const d=/^data:(image\/(?:png|jpeg|webp));base64,([a-zA-Z0-9+/=]+)$/.exec(f);if(!d)throw new Error("El ícono no tiene un formato válido.");const v=d[1],r=atob(d[2]),x=new Uint8Array(r.length);for(let N=0;Nr.app_id)}async function Vv(f){const{data:d,error:v}=await qe.from(vm).select("app_id,last_opened_at").eq("user_id",f).order("last_opened_at",{ascending:!1}).limit(30);if(v)throw new Error(`No se pudieron cargar los accesos recientes: ${v.message}`);return(d??[]).map(r=>r.app_id)}async function Kv(f){const[d,v,r]=await Promise.all([Hs(),Zv(f),Vv(f)]);return{schemaVersion:1,apps:d,favoritesByUser:{[f]:v},recentByUser:{[f]:r},updatedAt:new Date().toISOString()}}async function bm(f,d,v){const r=(d==null?void 0:d.id)??crypto.randomUUID(),x=await wv(r,f.iconDataUrl),N={name:f.name,description:f.description,category:f.category,url:f.url||null,icon_url:x,visibility:f.visibility,updated_by:v};let C;if(d?C=await qe.from(Dn).update(N).eq("id",r).select("id,name,description,category,url,icon_url,visibility,created_at,updated_at").single():C=await qe.from(Dn).insert({id:r,...N,created_by:v}).select("id,name,description,category,url,icon_url,visibility,created_at,updated_at").single(),C.error)throw x!==f.iconDataUrl&&await Ms(x),new Error(`No se pudo guardar la aplicación: ${C.error.message}`);return d&&d.iconDataUrl!==x&&await Ms(d.iconDataUrl),gm(C.data)}async function Jv(f){const{error:d}=await qe.from(Dn).delete().eq("id",f.id);if(d)throw new Error(`No se pudo eliminar la aplicación: ${d.message}`);await Ms(f.iconDataUrl)}async function kv(f,d,v){if(v){const{error:x}=await qe.from(Ds).upsert({user_id:f,app_id:d},{onConflict:"user_id,app_id"});if(x)throw new Error(`No se pudo guardar el favorito: ${x.message}`);return}const{error:r}=await qe.from(Ds).delete().eq("user_id",f).eq("app_id",d);if(r)throw new Error(`No se pudo quitar el favorito: ${r.message}`)}async function $v(f,d){const{error:v}=await qe.from(vm).upsert({user_id:f,app_id:d,last_opened_at:new Date().toISOString()},{onConflict:"user_id,app_id"});if(v)throw new Error(`No se pudo registrar el acceso reciente: ${v.message}`)}async function Wv(f,d){if(f.role!=="admin"||d.length===0||localStorage.getItem(Pd)==="completed")return 0;const v=await Hs(),r=new Set(v.map(C=>C.name.trim().toLocaleLowerCase("es-DO"))),x=d.filter(C=>{const w=C.name.trim().toLocaleLowerCase("es-DO");return!!C.iconDataUrl.trim()&&!r.has(w)});let N=0;for(const C of x)await bm({name:C.name,description:C.description,category:C.category,url:C.url,iconDataUrl:C.iconDataUrl,visibility:C.visibility},null,f.id),r.add(C.name.trim().toLocaleLowerCase("es-DO")),N+=1;return localStorage.setItem(Pd,"completed"),N}function Fv(f){const d=qe.channel("glm-hub-apps-shared").on("postgres_changes",{event:"*",schema:"public",table:Dn},()=>f()).subscribe();return()=>{qe.removeChannel(d)}}const Us=new Intl.Collator("es-DO",{sensitivity:"base"}),Iv="@gomezleemarketing.com",em="https://agenteit.digitalcompass.agency/webhook/glm-hub-generar-icono".trim()??"",tm="https://agenteit.digitalcompass.agency/webhook/glm-hub-solicitar-acceso".trim()??"";function Ns(f=""){return`/${f}`}function Pv(){return new URL("/",window.location.origin).toString()}function e0(f,d){var N;const v=f.user.user_metadata??{},r=v.full_name??v.name,x=d.email.split("@")[0]||"Usuario GLM";return((N=d.full_name)==null?void 0:N.trim())||(typeof r=="string"?r.trim():"")||x}function t0(f){const d=f.user.user_metadata??{},v=d.avatar_url??d.picture;if(typeof v!="string"||!v.trim())return"";try{const r=new URL(v.trim());return r.protocol==="https:"?r.toString():""}catch{return""}}async function l0(f){var N;const d=((N=f.user.email)==null?void 0:N.trim().toLocaleLowerCase("en-US"))??"";if(!d.endsWith(Iv))return{session:null,error:"Debes continuar con una cuenta corporativa @gomezleemarketing.com."};const{data:v,error:r}=await qe.from("glm_hub_authorized_users").select("email, full_name, role").eq("email",d).eq("is_active",!0).maybeSingle(),x=v;return r?(console.error("No se pudo validar el acceso en Supabase:",r),{session:null,error:"No pudimos validar tu acceso en Supabase. Intenta nuevamente."}):x?{session:{user:{id:f.user.id,name:e0(f,x),email:x.email,role:x.role,avatarUrl:t0(f)},expiresAt:(f.expires_at??Math.floor(Date.now()/1e3)+3600)*1e3},error:""}:{session:null,error:"Tu correo no está autorizado para entrar al GLM Hub."}}function pm(f){const d=f.trim().split(/\s+/).filter(Boolean);if(d.length===0)return"GL";if(d.length===1)return d[0].slice(0,2).toLocaleUpperCase("es-DO");const v=d.length>=4?d.length-2:d.length-1;return`${d[0][0]}${d[v][0]}`.toLocaleUpperCase("es-DO")}function a0(){const f=new Date().getHours();return f<12?"Buenos días":f<19?"Buenas tardes":"Buenas noches"}function lm(f){return f&&f[0].toLocaleUpperCase("es-DO")+f.slice(1)}function am(f){const d=new Intl.DateTimeFormat("es-DO",{weekday:"long",day:"numeric",month:"long",year:"numeric"}).formatToParts(f),v=r=>{var x;return((x=d.find(N=>N.type===r))==null?void 0:x.value)??""};return`${lm(v("weekday"))} ${v("day")} de ${lm(v("month"))} de ${v("year")}`}function n0(){const[f,d]=H.useState(()=>am(new Date));return H.useEffect(()=>{const v=()=>d(am(new Date));v();const r=window.setInterval(v,6e4);return()=>window.clearInterval(r)},[]),f}function Wu(){return{name:"",description:"",category:"Administración",url:"",iconDataUrl:"",visibility:"published"}}function On(f){return{name:f.name,description:f.description,category:f.category,url:f.url,iconDataUrl:f.iconDataUrl,visibility:f.visibility}}async function Sm(f,d=!0){if(!new Set(["image/png","image/jpeg","image/webp"]).has(f.type))throw new Error("Usa un ícono en formato PNG, JPG o WebP.");if(d&&f.size>1024*1024)throw new Error("El ícono debe pesar menos de 1 MB.");const r=await createImageBitmap(f);if(r.width>4096||r.height>4096)throw r.close(),new Error("El ícono no puede superar 4096 × 4096 px.");const x=document.createElement("canvas");x.width=192,x.height=192;const N=x.getContext("2d");if(!N)throw r.close(),new Error("Este navegador no pudo procesar el ícono.");const C=Math.min(192/r.width,192/r.height),w=r.width*C,U=r.height*C,E=(192-w)/2,G=(192-U)/2;return N.clearRect(0,0,192,192),N.drawImage(r,E,G,w,U),r.close(),x.toDataURL("image/webp",.9)}async function u0(f){if(!f.startsWith("data:image/"))throw new Error("La IA no devolvió una imagen válida.");const v=await(await fetch(f)).blob();return _m(v)}async function _m(f){const d=f.type||"image/png";if(!d.startsWith("image/"))throw new Error("El flujo de IA no devolvió un archivo de imagen.");const v=d==="image/jpeg"?"jpg":d.split("/")[1]||"png",r=new File([f],`icono-generado.${v}`,{type:d});return Sm(r,!1)}function ei({compact:f=!1}){return c.jsx("img",{className:f?"brand-logo brand-logo--compact":"brand-logo",src:"/glm-logo.png",alt:"GomezLee Marketing",width:"350",height:"109"})}function ti({app:f,size:d="regular",showPlaceholder:v=!1}){const[r,x]=H.useState(!1),N=!!f.iconDataUrl&&!r;return c.jsx("span",{className:`app-mark app-mark--${d}`,"aria-hidden":"true",children:N?c.jsx("img",{src:f.iconDataUrl,alt:"",onError:()=>x(!0)}):v?c.jsx(yv,{className:"app-mark__placeholder",size:36,strokeWidth:1.5}):null})}function Em({user:f,label:d}){const[v,r]=H.useState(!1),x=!!f.avatarUrl&&!v;return c.jsx("span",{className:"avatar","aria-label":d,"aria-hidden":d?void 0:!0,children:x?c.jsx("img",{src:f.avatarUrl,alt:"",referrerPolicy:"no-referrer",onError:()=>r(!0)}):pm(f.name)})}function Ts({messages:f,onDismiss:d}){return c.jsx("div",{className:"toast-stack","aria-live":"polite","aria-relevant":"additions",children:f.map(v=>c.jsxs("div",{className:`toast toast--${v.kind}`,children:[c.jsx("span",{className:"toast__signal","aria-hidden":"true"}),c.jsxs("div",{children:[c.jsx("strong",{children:v.title}),v.detail?c.jsx("p",{children:v.detail}):null]}),c.jsx("button",{type:"button",className:"icon-button icon-button--quiet","aria-label":"Cerrar mensaje",onClick:()=>d(v.id),children:c.jsx(Un,{size:18,"aria-hidden":"true"})})]},v.id))})}function i0({onLogin:f,error:d,disabled:v=!1}){const[r,x]=H.useState(!1),N=async()=>{x(!0),await f(),x(!1)};return c.jsxs("main",{className:"login-shell",children:[c.jsxs("section",{className:"login-story","aria-labelledby":"login-headline",children:[c.jsx("div",{className:"login-story__topline",children:c.jsx(ei,{})}),c.jsxs("div",{className:"login-story__content",children:[c.jsx("p",{className:"eyebrow",children:"ACCESO INTERNO"}),c.jsxs("h1",{id:"login-headline",children:["Todo el trabajo",c.jsx("span",{children:"de GLM, a un clic."})]}),c.jsx("p",{className:"login-story__lede",children:"Tu punto de partida para abrir herramientas, encontrar recursos y mantener el día en movimiento."})]}),c.jsx("div",{className:"login-story__type","aria-hidden":"true",children:"GLM"})]}),c.jsx("section",{className:"login-panel","aria-label":"Inicio de sesión",children:c.jsxs("div",{className:"login-panel__inner",children:[c.jsxs("div",{className:"login-panel__heading",children:[c.jsx("p",{className:"section-kicker",children:"GLM HUB"}),c.jsx("h2",{children:"Entra a tu espacio."})]}),c.jsxs("div",{className:"login-form",children:[d?c.jsx("p",{className:"form-error",id:"login-error",role:"alert",children:d}):null,c.jsxs("button",{className:"primary-button primary-button--full",type:"button",disabled:v||r,onClick:()=>void N(),children:[c.jsx(bv,{size:18,"aria-hidden":"true"}),r?"Conectando…":"Continuar con Google",r?null:c.jsx(Mn,{size:18,"aria-hidden":"true"})]})]})]})})]})}function c0({user:f,view:d,onNavigate:v,onLogout:r}){return c.jsxs("aside",{className:"sidebar",children:[c.jsx("div",{className:"sidebar__brand",children:c.jsx(ei,{compact:!0})}),c.jsxs("nav",{className:"sidebar__nav","aria-label":"Navegación principal",children:[c.jsxs("button",{type:"button",className:d==="hub"?"is-active":"",onClick:()=>v("hub"),"aria-current":d==="hub"?"page":void 0,children:[c.jsx(cm,{size:20,"aria-hidden":"true"}),c.jsx("span",{children:"Aplicaciones"})]}),f.role==="admin"?c.jsxs("button",{type:"button",className:d==="admin"?"is-active":"",onClick:()=>v("admin"),"aria-current":d==="admin"?"page":void 0,children:[c.jsx(om,{size:20,"aria-hidden":"true"}),c.jsx("span",{children:"Administrar"})]}):null]}),c.jsxs("div",{className:"sidebar__account",children:[c.jsx(Em,{user:f}),c.jsxs("span",{className:"sidebar__account-copy",children:[c.jsx("strong",{children:f.name}),c.jsx("small",{children:f.role==="admin"?"Administrador":"Usuario"})]}),c.jsx("button",{type:"button",className:"icon-button","aria-label":"Cerrar sesión",onClick:r,children:c.jsx(fm,{size:18,"aria-hidden":"true"})})]})]})}function s0({user:f}){return c.jsxs("header",{className:"mobile-header",children:[c.jsx(ei,{compact:!0}),c.jsx(Em,{user:f,label:`Sesión de ${f.name}`})]})}function f0({user:f,view:d,onNavigate:v,onLogout:r}){return c.jsxs("nav",{className:"mobile-nav","aria-label":"Navegación móvil",children:[c.jsxs("button",{type:"button",className:d==="hub"?"is-active":"",onClick:()=>v("hub"),children:[c.jsx(cm,{size:20,"aria-hidden":"true"}),c.jsx("span",{children:"Inicio"})]}),f.role==="admin"?c.jsxs("button",{type:"button",className:d==="admin"?"is-active":"",onClick:()=>v("admin"),children:[c.jsx(om,{size:20,"aria-hidden":"true"}),c.jsx("span",{children:"Administrar"})]}):null,c.jsxs("button",{type:"button",onClick:r,children:[c.jsx(fm,{size:20,"aria-hidden":"true"}),c.jsx("span",{children:"Salir"})]})]})}function r0({app:f,isFavorite:d,onActivate:v,onFavorite:r}){const x=!!Jl(f.url);return c.jsxs("article",{className:"quick-launch",children:[c.jsxs("div",{className:"quick-launch__top",children:[c.jsx(ti,{app:f,size:"regular"}),c.jsx("button",{type:"button",className:`favorite-button ${d?"is-favorite":""}`,onClick:()=>r(f.id),"aria-label":d?`Quitar ${f.name} de favoritos`:`Agregar ${f.name} a favoritos`,"aria-pressed":d,children:c.jsx(qs,{size:18,fill:d?"currentColor":"none","aria-hidden":"true"})})]}),c.jsxs("div",{className:"quick-launch__copy",children:[c.jsx("span",{className:"quick-launch__category",children:f.category}),c.jsx("h3",{children:f.name}),c.jsx("p",{children:f.description})]}),c.jsxs("button",{type:"button",className:"quick-launch__action",onClick:()=>v(f),children:[c.jsx("span",{children:x?"Abrir aplicación":"Enlace pendiente"}),x?c.jsx(Mn,{size:18,"aria-hidden":"true"}):c.jsx(sm,{size:17,"aria-hidden":"true"})]})]})}function o0({app:f,index:d,isFavorite:v,onActivate:r,onFavorite:x}){const N=!!Jl(f.url);return c.jsxs("article",{className:"directory-row",children:[c.jsx("span",{className:"directory-row__number","aria-hidden":"true",children:String(d+1).padStart(2,"0")}),c.jsx(ti,{app:f,size:"small"}),c.jsxs("div",{className:"directory-row__copy",children:[c.jsx("h3",{children:f.name}),c.jsx("p",{children:f.description})]}),c.jsx("span",{className:"directory-row__category",children:f.category}),c.jsxs("span",{className:`availability ${N?"availability--ready":"availability--pending"}`,children:[c.jsx("span",{"aria-hidden":"true"}),N?"Disponible":"Enlace pendiente"]}),c.jsx("button",{type:"button",className:`favorite-button ${v?"is-favorite":""}`,onClick:()=>x(f.id),"aria-label":v?`Quitar ${f.name} de favoritos`:`Agregar ${f.name} a favoritos`,"aria-pressed":v,children:c.jsx(qs,{size:18,fill:v?"currentColor":"none","aria-hidden":"true"})}),c.jsxs("button",{type:"button",className:"directory-row__open",onClick:()=>r(f),children:[c.jsx("span",{children:N?"Abrir":"Ver estado"}),N?c.jsx(Mn,{size:17,"aria-hidden":"true"}):c.jsx(im,{size:17,"aria-hidden":"true"})]})]})}function d0({search:f,favoritesOnly:d,onClear:v}){return c.jsxs("div",{className:"empty-state",children:[c.jsx("span",{className:"empty-state__mark","aria-hidden":"true",children:"0"}),c.jsxs("div",{children:[c.jsx("p",{className:"section-kicker",children:"SIN RESULTADOS"}),c.jsx("h3",{children:d?"Aún no tienes favoritos aquí.":`No encontramos “${f||"esa aplicación"}”.`}),c.jsx("p",{children:"Prueba otro término, cambia la categoría o vuelve a ver todo el directorio."}),c.jsx("button",{type:"button",className:"text-button",onClick:v,children:"Limpiar filtros"})]})]})}function m0({apps:f,onSubmit:d}){const v=H.useRef(null),[r,x]=H.useState("Todas"),[N,C]=H.useState(""),[w,U]=H.useState(!1),E=H.useMemo(()=>[...f].filter(Z=>Z.visibility==="published").sort((Z,ee)=>Us.compare(Z.name,ee.name)),[f]),G=H.useMemo(()=>r==="Todas"?E:E.filter(Z=>Z.category===r),[r,E]);H.useEffect(()=>{N&&!G.some(Z=>Z.id===N)&&C("")},[N,G]),H.useEffect(()=>{if(!w)return;const Z=ee=>{ee.preventDefault(),ee.returnValue=""};return window.addEventListener("beforeunload",Z),()=>window.removeEventListener("beforeunload",Z)},[w]);const B=()=>{var Z;x("Todas"),C(""),(Z=v.current)==null||Z.showModal()},J=()=>{var Z;w||(Z=v.current)==null||Z.close()},ye=async Z=>{var V;Z.preventDefault();const ee=E.find(ue=>ue.id===N);if(!(!ee||w)){U(!0);try{await d(ee)&&((V=v.current)==null||V.close(),x("Todas"),C(""))}finally{U(!1)}}};return c.jsxs("section",{className:"access-request-bridge","aria-label":"Solicitud de acceso a aplicaciones",children:[c.jsx("div",{className:"access-request-bridge__number","aria-hidden":"true",children:"?"}),c.jsxs("div",{children:[c.jsx("p",{className:"section-kicker",children:"SOLICITUD DE ACCESO"}),c.jsx("h2",{children:"¿No tienes acceso a una aplicación?"}),c.jsx("p",{children:"Selecciona el portal que necesitas y envía la solicitud al equipo responsable."})]}),c.jsxs("button",{type:"button",className:"secondary-button",onClick:B,children:["Solicitar acceso aquí",c.jsx(Mn,{size:18,"aria-hidden":"true"})]}),c.jsxs("dialog",{className:"access-request-dialog",ref:v,onCancel:Z=>{w&&Z.preventDefault()},children:[c.jsx("button",{type:"button",className:"access-request-dialog__close",onClick:J,"aria-label":"Cerrar",children:c.jsx(Un,{size:19,"aria-hidden":"true"})}),c.jsx("p",{className:"section-kicker",children:"GLM HUB"}),c.jsx("h2",{children:"Solicitar acceso"}),c.jsx("p",{className:"access-request-dialog__intro",children:"Elige el área y la aplicación. El catálogo se actualiza automáticamente con los portales publicados."}),c.jsxs("form",{onSubmit:Z=>void ye(Z),children:[c.jsxs("div",{className:"form-field",children:[c.jsxs("label",{htmlFor:"access-request-area",children:["Área",c.jsx(Kl,{})]}),c.jsxs("select",{id:"access-request-area",value:r,onChange:Z=>x(Z.target.value),disabled:w,required:!0,children:[c.jsx("option",{value:"Todas",children:"Todas"}),c.jsx("option",{value:"Administración",children:"Administración"}),c.jsx("option",{value:"Recursos Humanos",children:"Recursos Humanos"}),c.jsx("option",{value:"CDC",children:"CDC"})]})]}),c.jsxs("div",{className:"form-field",children:[c.jsxs("label",{htmlFor:"access-request-app",children:["Aplicación",c.jsx(Kl,{})]}),c.jsxs("select",{id:"access-request-app",value:N,onChange:Z=>C(Z.target.value),disabled:w||G.length===0,required:!0,children:[c.jsx("option",{value:"",children:G.length===0?"No hay aplicaciones publicadas en esta área":"Selecciona una aplicación"}),G.map(Z=>c.jsx("option",{value:Z.id,children:Z.name},Z.id))]})]}),c.jsxs("div",{className:"access-request-dialog__actions",children:[c.jsx("button",{type:"button",className:"secondary-button secondary-button--quiet",onClick:J,disabled:w,children:"Cancelar"}),c.jsx("button",{type:"submit",className:"primary-button",disabled:!N||w,children:w?"Enviando…":"Enviar solicitud"})]})]})]}),w?c.jsx("div",{className:"generation-overlay",role:"dialog","aria-modal":"true","aria-labelledby":"access-request-loading-title",children:c.jsxs("div",{className:"generation-overlay__card",children:[c.jsx("span",{className:"generation-spinner","aria-hidden":"true"}),c.jsx("p",{className:"section-kicker",children:"GLM HUB"}),c.jsx("h2",{id:"access-request-loading-title",children:"Enviando la solicitud…"}),c.jsx("p",{children:"Estamos registrando la solicitud y enviando el correo a las personas responsables."})]})}):null]})}function h0(f,d,v=!1){return d<=(v?5:7)?Array.from({length:d},(x,N)=>N+1):v?f<=3?[1,2,3,"ellipsis-end",d]:f>=d-2?[1,"ellipsis-start",d-2,d-1,d]:[1,"ellipsis-start",f,"ellipsis-end",d]:f<=4?[1,2,3,4,5,"ellipsis-end",d]:f>=d-3?[1,"ellipsis-start",d-4,d-3,d-2,d-1,d]:[1,"ellipsis-start",f-1,f,f+1,"ellipsis-end",d]}function Cs({currentPage:f,totalPages:d,onPageChange:v,compact:r=!1,label:x}){if(d<=1)return null;const N=h0(f,d,r);return c.jsxs("nav",{className:`pagination ${r?"pagination--compact":""}`,"aria-label":x,children:[c.jsx("button",{type:"button",className:"pagination__arrow",disabled:f<=1,onClick:()=>v(f-1),"aria-label":"Página anterior",children:c.jsx(ov,{size:16,"aria-hidden":"true"})}),c.jsx("div",{className:"pagination__pages",children:N.map(C=>typeof C=="number"?c.jsx("button",{type:"button",className:`pagination__page ${C===f?"is-active":""}`,onClick:()=>v(C),"aria-label":`Ir a la página ${C}`,"aria-current":C===f?"page":void 0,children:C},C):c.jsx("span",{className:"pagination__ellipsis","aria-hidden":"true",children:"…"},C))}),c.jsx("button",{type:"button",className:"pagination__arrow",disabled:f>=d,onClick:()=>v(f+1),"aria-label":"Página siguiente",children:c.jsx(im,{size:16,"aria-hidden":"true"})})]})}const Fu=10,xs=6,Os=10;function y0({user:f,data:d,storageWarning:v,onActivate:r,onFavorite:x,onAdmin:N,onRequestAccess:C}){const[w,U]=H.useState(""),[E,G]=H.useState("Todas"),[B,J]=H.useState(!1),[ye,Z]=H.useState(1),[ee,V]=H.useState(1),ue=H.useDeferredValue(w),Me=n0(),Te=f.role==="admin",ae=d.favoritesByUser[f.id]??[],ve=d.recentByUser[f.id]??[],ge=H.useMemo(()=>new Set(ae),[ae]),W=H.useMemo(()=>[...d.apps.filter(g=>g.visibility==="published"&&!!g.iconDataUrl.trim())].sort((g,M)=>Us.compare(g.name,M.name)),[d.apps]),ne=H.useMemo(()=>{const g=Ua(ue);return W.filter(M=>E!=="Todas"&&M.category!==E||B&&!ge.has(M.id)?!1:g?Ua(M.name).includes(g):!0).sort((M,R)=>Number(ge.has(R.id))-Number(ge.has(M.id))||Us.compare(M.name,R.name))},[E,ue,ge,B,W]),Ue=Math.max(1,Math.ceil(ne.length/Fu));H.useEffect(()=>{Z(1)},[w,E,B]),H.useEffect(()=>{ye>Ue&&Z(Ue)},[ye,Ue]);const Je=H.useMemo(()=>{const g=(ye-1)*Fu;return ne.slice(g,g+Fu)},[ye,ne]),xe=g=>{Z(Math.min(Math.max(g,1),Ue)),window.requestAnimationFrame(()=>{var M;(M=document.getElementById("directory-title"))==null||M.scrollIntoView({behavior:"smooth",block:"start"})})},De=H.useMemo(()=>{const g=new Map(W.map(o=>[o.id,o])),M=[],R=new Set,z=o=>{const j=g.get(o);!j||R.has(o)||(M.push(j),R.add(o))};if(ae.forEach(z),M.length<3){for(const o of ve)if(z(o),M.length>=3)break}if(M.length<3){for(const o of W)if(z(o.id),M.length>=3)break}return M},[ae,W,ve]),Xe=Math.max(1,Math.ceil(De.length/xs)),Q=H.useMemo(()=>{const g=(ee-1)*xs;return De.slice(g,g+xs)},[De,ee]);H.useEffect(()=>{ee>Xe&&V(Xe)},[ee,Xe]),H.useEffect(()=>{V(1)},[d.favoritesByUser,d.recentByUser,f.id]);const I=g=>{V(Math.min(Math.max(g,1),Xe)),window.requestAnimationFrame(()=>{var M;(M=document.getElementById("quick-title"))==null||M.scrollIntoView({behavior:"smooth",block:"start"})})},S=()=>{U(""),G("Todas"),J(!1)};return c.jsxs("main",{className:"page-content hub-page",children:[c.jsxs("header",{className:"page-header",children:[c.jsxs("div",{children:[c.jsxs("p",{className:"page-header__meta",children:[a0(),", ",f.name]}),c.jsx("h1",{children:"Tu mesa de trabajo."})]}),c.jsx("div",{className:"page-header__aside",children:c.jsx("span",{children:Me})})]}),v?c.jsx("div",{className:"inline-alert",role:"status",children:v}):null,c.jsxs("section",{className:"search-stage","aria-labelledby":"search-title",children:[c.jsx("div",{className:"search-stage__copy",children:c.jsx("h2",{id:"search-title",children:"¿Qué necesitas abrir?"})}),c.jsxs("div",{className:"hub-search",children:[c.jsx(rm,{size:21,"aria-hidden":"true"}),c.jsx("label",{className:"sr-only",htmlFor:"hub-search-input",children:"Buscar por nombre de proyecto"}),c.jsx("input",{id:"hub-search-input",type:"search",value:w,onChange:g=>U(g.target.value),placeholder:"Buscar por nombre de proyecto…"}),w?c.jsx("button",{type:"button",onClick:()=>U(""),"aria-label":"Limpiar búsqueda",children:c.jsx(Un,{size:18,"aria-hidden":"true"})}):null]})]}),c.jsxs("section",{className:"quick-section","aria-labelledby":"quick-title",children:[c.jsxs("div",{className:"section-heading",children:[c.jsxs("div",{children:[c.jsx("p",{className:"section-kicker",children:"FIJADAS PARA TI"}),c.jsx("h2",{id:"quick-title",children:"Acceso rápido"})]}),c.jsx("p",{children:"Abre lo que más usas sin detener el ritmo."})]}),c.jsx("div",{className:"quick-grid",children:Q.map(g=>c.jsx(r0,{app:g,isFavorite:ge.has(g.id),onActivate:r,onFavorite:x},g.id))}),c.jsx(Cs,{currentPage:ee,totalPages:Xe,onPageChange:I,compact:Xe>5,label:"Paginación de acceso rápido"})]}),c.jsxs("section",{className:"directory-section","aria-labelledby":"directory-title",children:[c.jsxs("div",{className:"section-heading section-heading--directory",children:[c.jsxs("div",{children:[c.jsx("p",{className:"section-kicker",children:"DIRECTORIO GLM"}),c.jsx("h2",{id:"directory-title",children:"Todas las aplicaciones"})]}),c.jsxs("div",{className:"directory-count","aria-live":"polite",children:[c.jsx("strong",{children:ne.length}),c.jsx("span",{children:ne.length===1?"resultado":"resultados"})]})]}),c.jsxs("div",{className:"filter-bar","aria-label":"Filtros del directorio",children:[c.jsx("div",{className:"category-filters",children:["Todas",...dm].map(g=>c.jsx("button",{type:"button",className:E===g?"is-active":"",onClick:()=>G(g),"aria-pressed":E===g,children:g},g))}),c.jsxs("button",{type:"button",className:`favorites-filter ${B?"is-active":""}`,onClick:()=>J(g=>!g),"aria-pressed":B,children:[c.jsx(qs,{size:17,fill:B?"currentColor":"none","aria-hidden":"true"}),"Solo favoritos"]})]}),ne.length>0?c.jsxs(c.Fragment,{children:[c.jsx("div",{className:"directory-list",children:Je.map((g,M)=>c.jsx(o0,{app:g,index:(ye-1)*Fu+M,isFavorite:ge.has(g.id),onActivate:r,onFavorite:x},g.id))}),c.jsx(Cs,{currentPage:ye,totalPages:Ue,onPageChange:xe,compact:Ue>5,label:"Paginación de aplicaciones"})]}):c.jsx(d0,{search:w,favoritesOnly:B,onClear:S})]}),Te?c.jsxs("section",{className:"admin-bridge","aria-label":"Acceso al panel de administración",children:[c.jsx("div",{className:"admin-bridge__number","aria-hidden":"true",children:"+"}),c.jsxs("div",{children:[c.jsx("p",{className:"section-kicker",children:"CONTROL DEL CATÁLOGO"}),c.jsx("h2",{children:"¿Falta una herramienta?"}),c.jsx("p",{children:"Adjunta su ícono, pega el enlace y publícala para todo el equipo."})]}),c.jsxs("button",{type:"button",className:"secondary-button",onClick:N,children:["Administrar aplicaciones",c.jsx(Mn,{size:18,"aria-hidden":"true"})]})]}):c.jsx(m0,{apps:W,onSubmit:C})]})}function v0({apps:f,selectedId:d,onSelect:v,onDelete:r}){const[x,N]=H.useState(""),[C,w]=H.useState(1),[U,E]=H.useState(null),G=H.useRef(null),B=H.useMemo(()=>{const V=Ua(x);return V?f.filter(ue=>Ua(`${ue.name} ${ue.category}`).includes(V)):f},[f,x]),J=Math.max(1,Math.ceil(B.length/Os));H.useEffect(()=>{w(1)},[x]),H.useEffect(()=>{C>J&&w(J)},[C,J]);const ye=H.useMemo(()=>{const V=(C-1)*Os;return B.slice(V,V+Os)},[B,C]),Z=(V,ue)=>{var Me;V.stopPropagation(),E(ue),(Me=G.current)==null||Me.showModal()},ee=async()=>{var V;U&&await r(U.id)&&((V=G.current)==null||V.close(),E(null))};return c.jsxs("aside",{className:"admin-list","aria-label":"Aplicaciones configuradas",children:[c.jsxs("div",{className:"admin-list__top",children:[c.jsxs("div",{children:[c.jsx("p",{className:"section-kicker",children:"CATÁLOGO"}),c.jsxs("h2",{children:[f.length," ",f.length===1?"aplicación":"aplicaciones"]})]}),c.jsx("button",{type:"button",className:"icon-button icon-button--accent",onClick:()=>v(null),"aria-label":"Crear aplicación",children:c.jsx(Fd,{size:20,"aria-hidden":"true"})})]}),c.jsxs("div",{className:"admin-list__search",children:[c.jsx(rm,{size:17,"aria-hidden":"true"}),c.jsx("label",{htmlFor:"admin-search",className:"sr-only",children:"Buscar en el catálogo"}),c.jsx("input",{id:"admin-search",type:"search",value:x,onChange:V=>N(V.target.value),placeholder:"Buscar…"})]}),c.jsxs("div",{className:"admin-list__items",children:[C===1?c.jsxs("button",{type:"button",className:`admin-list__new ${d===null?"is-active":""}`,onClick:()=>v(null),children:[c.jsx("span",{className:"admin-list__new-icon",children:c.jsx(Fd,{size:18,"aria-hidden":"true"})}),c.jsxs("span",{className:"admin-list__item-info",children:[c.jsx("strong",{children:"Nueva aplicación"}),c.jsx("small",{children:"Crear y publicar"})]})]}):null,ye.map(V=>c.jsxs("div",{className:`admin-list__item-row ${d===V.id?"is-active":""}`,onClick:()=>v(V.id),role:"button",tabIndex:0,onKeyDown:ue=>{(ue.key==="Enter"||ue.key===" ")&&v(V.id)},children:[c.jsx(ti,{app:V,size:"small"}),c.jsxs("span",{className:"admin-list__item-info",children:[c.jsx("strong",{children:V.name}),c.jsx("small",{children:V.category})]}),c.jsxs("div",{className:"admin-list__item-actions",children:[c.jsx("span",{className:`mini-status ${V.visibility==="hidden"?"is-hidden":""}`,children:V.visibility==="hidden"?"Oculta":Jl(V.url)?"Lista":"Pendiente"}),c.jsx("button",{type:"button",className:"admin-list__delete-icon",onClick:ue=>Z(ue,V),title:`Eliminar ${V.name}`,"aria-label":`Eliminar ${V.name}`,children:c.jsx(Iu,{size:15,"aria-hidden":"true"})})]})]},V.id))]}),c.jsx(Cs,{currentPage:C,totalPages:J,onPageChange:w,compact:!0,label:"Paginación del catálogo de administración"}),c.jsxs("dialog",{className:"confirm-dialog",ref:G,children:[c.jsx("button",{type:"button",className:"confirm-dialog__close",onClick:()=>{var V;return(V=G.current)==null?void 0:V.close()},"aria-label":"Cerrar",children:c.jsx(Un,{size:19,"aria-hidden":"true"})}),c.jsx("span",{className:"confirm-dialog__icon",children:c.jsx(Iu,{size:22,"aria-hidden":"true"})}),c.jsx("p",{className:"section-kicker",children:"ACCIÓN PERMANENTE"}),c.jsxs("h2",{children:["Eliminar ",U==null?void 0:U.name]}),c.jsx("p",{children:"El portal y sus accesos se eliminarán permanentemente. Esta acción no se puede deshacer."}),c.jsxs("div",{className:"confirm-dialog__actions",children:[c.jsx("button",{type:"button",className:"secondary-button",onClick:()=>{var V;return(V=G.current)==null?void 0:V.close()},children:"Conservar"}),c.jsx("button",{type:"button",className:"danger-button danger-button--solid",onClick:ee,children:"Sí, eliminar"})]})]})]})}function Kl(){return c.jsxs(c.Fragment,{children:[c.jsx("span",{className:"required-mark","aria-hidden":"true",children:"*"}),c.jsx("span",{className:"sr-only",children:" obligatorio"})]})}function g0({app:f,allApps:d,onSave:v,onDelete:r,onCancel:x}){const[N,C]=H.useState(()=>f?On(f):Wu()),[w,U]=H.useState(()=>JSON.stringify(f?On(f):Wu())),[E,G]=H.useState({}),[B,J]=H.useState(""),[ye,Z]=H.useState(!1),[ee,V]=H.useState(!1),[ue,Me]=H.useState(!1),[Te,ae]=H.useState(!1),ve=H.useRef(null),ge=H.useRef(null),W=JSON.stringify(N)!==w,ne=ye||ee||ue,Ue=N.name.trim().length>=2&&!!N.description.trim();H.useEffect(()=>{const z=f?On(f):Wu();C(z),U(JSON.stringify(z)),G({}),J(""),window.requestAnimationFrame(()=>{var o;return(o=ge.current)==null?void 0:o.focus()})},[f]),H.useEffect(()=>{if(!W&&!ee)return;const z=o=>o.preventDefault();return window.addEventListener("beforeunload",z),()=>window.removeEventListener("beforeunload",z)},[ee,W]),H.useEffect(()=>{if(!ee)return;const z=document.body.style.overflow;return document.body.style.overflow="hidden",()=>{document.body.style.overflow=z}},[ee]);const Je={id:(f==null?void 0:f.id)??"preview",name:N.name||"Nombre de la aplicación",description:N.description||"Una descripción breve ayuda al equipo a reconocerla.",category:N.category,keywords:[],url:N.url,mark:pm(N.name||"GLM"),accent:(f==null?void 0:f.accent)??"#4F758B",iconDataUrl:N.iconDataUrl,visibility:N.visibility,createdAt:(f==null?void 0:f.createdAt)??new Date().toISOString(),updatedAt:new Date().toISOString()},xe=(z,o)=>{C(j=>({...j,[z]:o})),G(j=>({...j,[z]:""}))},De=async z=>{if(!(!z||ee)){J(""),Z(!0);try{const o=await Sm(z);xe("iconDataUrl",o)}catch(o){J(o instanceof Error?o.message:"No pudimos procesar el ícono.")}finally{Z(!1)}}},Xe=z=>{var o;De((o=z.target.files)==null?void 0:o[0]),z.target.value=""},Q=z=>{var o;z.preventDefault(),ae(!1),De((o=z.dataTransfer.files)==null?void 0:o[0])},I=async()=>{var F;const z=N.name.trim(),o=N.description.trim(),j={};if(z.length<2&&(j.name="Completa el nombre antes de generar el ícono."),o||(j.description="Completa la descripción antes de generar el ícono."),Object.keys(j).length>0){G(k=>({...k,...j}));return}if(!em){J("Falta configurar VITE_ICON_GENERATOR_WEBHOOK_URL en el archivo .env.");return}J(""),V(!0);const q=new AbortController,L=window.setTimeout(()=>q.abort(),18e4);try{const{data:{session:k}}=await qe.auth.getSession();if(!(k!=null&&k.access_token))throw new Error("La sesión venció. Inicia sesión nuevamente.");const ie=await fetch(em,{method:"POST",mode:"cors",cache:"no-store",credentials:"omit",signal:q.signal,headers:{Accept:"image/*, application/json;q=0.9, text/plain;q=0.8","Content-Type":"text/plain;charset=UTF-8"},body:JSON.stringify({accessToken:k.access_token,name:z,description:o})}),ke=((F=ie.headers.get("content-type"))==null?void 0:F.toLocaleLowerCase("en-US"))??"";if(ie.ok&&ke.startsWith("image/")){const tl=await _m(await ie.blob());xe("iconDataUrl",tl);return}const Ae=await ie.text();let et=null;if(Ae)try{et=JSON.parse(Ae)}catch{et=null}if(ie.ok&&(et!=null&&et.ok)&&et.imageDataUrl){const tl=await u0(et.imageDataUrl);xe("iconDataUrl",tl);return}throw new Error((et==null?void 0:et.error)||(Ae&&Ae.length<500?Ae:"")||`El flujo no pudo generar el ícono (HTTP ${ie.status}).`)}catch(k){k instanceof DOMException&&k.name==="AbortError"?J("La generación tardó más de 3 minutos y fue cancelada. Intenta nuevamente."):k instanceof TypeError&&/failed to fetch/i.test(k.message)?J("No se pudo conectar con el webhook de n8n. Verifica que el workflow esté activo y publicado."):J(k instanceof Error?k.message:"No pudimos generar el ícono con IA.")}finally{window.clearTimeout(L),V(!1)}},S=()=>{const z={},o=N.name.trim(),j=N.description.trim();(o.length<2||o.length>60)&&(z.name="Escribe un nombre de 2 a 60 caracteres."),j||(z.description="Añade una descripción breve."),j.length>180&&(z.description="La descripción no puede superar 180 caracteres."),N.iconDataUrl||(z.iconDataUrl="Adjunta o genera un ícono antes de publicar la aplicación."),N.url.trim()&&!Jl(N.url)&&(z.url="Usa una URL https:// válida, sin credenciales embebidas.");const q=Ua(o);return d.some(F=>F.id!==(f==null?void 0:f.id)&&Ua(F.name)===q)&&(z.name="Ya existe una aplicación con ese nombre."),G(z),Object.keys(z).length===0},g=async z=>{if(z.preventDefault(),ne||!S()){window.requestAnimationFrame(()=>{var o,j;return(j=(o=document.querySelector(".field-error"))==null?void 0:o.focus)==null?void 0:j.call(o)});return}Me(!0);try{const o=await v({...N,name:N.name.trim(),description:N.description.trim(),url:N.url.trim()},(f==null?void 0:f.id)??null);if(o){const j=On(o);C(j),U(JSON.stringify(j))}}finally{Me(!1)}},M=()=>{if(!ee&&(!W||window.confirm("Hay cambios sin guardar. ¿Quieres descartarlos?"))){const z=f?On(f):Wu();C(z),U(JSON.stringify(z)),G({}),J(""),x()}},R=async()=>{var z;!f||ne||await r(f.id)&&((z=ve.current)==null||z.close(),x())};return c.jsxs("section",{className:"app-editor","aria-labelledby":"editor-title","aria-busy":ne,children:[c.jsxs("div",{className:"app-editor__heading",children:[c.jsxs("div",{children:[c.jsx("p",{className:"section-kicker",children:f?"EDITAR APLICACIÓN":"NUEVA APLICACIÓN"}),c.jsx("h2",{id:"editor-title",children:f?f.name:"Configura el nuevo acceso"})]}),W?c.jsxs("span",{className:"dirty-state is-dirty",children:[c.jsx("span",{"aria-hidden":"true"}),"Cambios sin guardar"]}):null]}),c.jsxs("div",{className:"editor-layout",children:[c.jsxs("form",{className:"editor-form",onSubmit:g,noValidate:!0,children:[c.jsxs("section",{className:"editor-form__section","aria-labelledby":"identity-section-title",children:[c.jsx("h3",{className:"editor-form__section-title",id:"identity-section-title",children:"Identidad"}),c.jsxs("div",{className:"form-field",children:[c.jsxs("label",{htmlFor:"app-name",children:["Nombre de la aplicación",f?null:c.jsx(Kl,{})]}),c.jsx("input",{ref:ge,id:"app-name",value:N.name,onChange:z=>xe("name",z.target.value),placeholder:"Ej. Media Planner",maxLength:60,"aria-invalid":!!E.name,"aria-describedby":E.name?"app-name-error":void 0,disabled:ne,required:!0}),c.jsxs("div",{className:"field-meta",children:[E.name?c.jsx("span",{id:"app-name-error",className:"field-error",role:"alert",tabIndex:-1,children:E.name}):c.jsx("span",{children:"Como el equipo la reconoce."}),c.jsxs("span",{children:[N.name.length,"/60"]})]})]}),c.jsxs("div",{className:"form-field",children:[c.jsxs("label",{htmlFor:"app-description",children:["Descripción breve",f?null:c.jsx(Kl,{})]}),c.jsx("textarea",{id:"app-description",value:N.description,onChange:z=>xe("description",z.target.value),placeholder:"Explica qué resuelve en una sola frase.",maxLength:180,rows:3,"aria-invalid":!!E.description,"aria-describedby":E.description?"app-description-error":void 0,disabled:ne,required:!0}),c.jsxs("div",{className:`field-meta ${E.description?"":"field-meta--counter-only"}`,children:[E.description?c.jsx("span",{id:"app-description-error",className:"field-error",role:"alert",tabIndex:-1,children:E.description}):null,c.jsxs("span",{children:[N.description.length,"/180"]})]})]}),c.jsxs("div",{className:"form-split",children:[c.jsxs("div",{className:"form-field",children:[c.jsxs("label",{htmlFor:"app-category",children:["Categoría",f?null:c.jsx(Kl,{})]}),c.jsx("select",{id:"app-category",value:N.category,onChange:z=>xe("category",z.target.value),disabled:ne,required:!0,children:dm.map(z=>c.jsx("option",{children:z},z))})]}),c.jsxs("div",{className:"form-field",children:[c.jsxs("label",{htmlFor:"app-visibility",children:["Visibilidad",f?null:c.jsx(Kl,{})]}),c.jsxs("select",{id:"app-visibility",value:N.visibility,onChange:z=>xe("visibility",z.target.value),disabled:ne,required:!0,children:[c.jsx("option",{value:"published",children:"Publicada"}),c.jsx("option",{value:"hidden",children:"Oculta"})]})]})]})]}),c.jsxs("section",{className:"editor-form__section","aria-labelledby":"icon-section-title",children:[c.jsxs("h3",{className:"editor-form__section-title",id:"icon-section-title",children:["Ícono",f?null:c.jsx(Kl,{})]}),c.jsxs("div",{className:"icon-config",children:[c.jsxs("label",{className:`icon-dropzone ${Te?"is-dragging":""}`,onDragEnter:z=>{z.preventDefault(),ne||ae(!0)},onDragOver:z=>z.preventDefault(),onDragLeave:()=>ae(!1),onDrop:Q,children:[c.jsx("input",{type:"file",accept:"image/png,image/jpeg,image/webp",onChange:Xe,disabled:ne}),c.jsx("span",{className:"icon-dropzone__symbol",children:c.jsx(Dv,{size:20,"aria-hidden":"true"})}),c.jsxs("span",{children:[c.jsx("strong",{children:ye?"Procesando…":"Adjuntar ícono"}),c.jsx("small",{children:"PNG, JPG o WebP · máximo 1 MB"})]})]}),N.iconDataUrl?c.jsx("button",{type:"button",className:"text-button text-button--danger",disabled:ne,onClick:()=>xe("iconDataUrl",""),children:"Quitar ícono"}):null]}),c.jsx("div",{className:"ai-icon-actions",children:c.jsxs("button",{type:"button",className:"secondary-button ai-icon-button",onClick:()=>void I(),disabled:!Ue||ne,title:Ue?void 0:"Completa el nombre y la descripción breve.",children:[c.jsx(Nv,{size:18,"aria-hidden":"true"}),"Generar ícono con IA"]})}),B?c.jsx("p",{className:"field-error field-error--standalone",role:"alert",children:B}):null,E.iconDataUrl?c.jsx("p",{className:"field-error field-error--standalone",role:"alert",tabIndex:-1,children:E.iconDataUrl}):null]}),c.jsxs("section",{className:"editor-form__section","aria-labelledby":"destination-section-title",children:[c.jsx("h3",{className:"editor-form__section-title",id:"destination-section-title",children:"Destino"}),c.jsxs("div",{className:"form-field",children:[c.jsxs("label",{htmlFor:"app-url",children:["Enlace de la aplicación ",c.jsx("span",{children:"Opcional"})]}),c.jsxs("div",{className:"url-field",children:[c.jsx(sm,{size:18,"aria-hidden":"true"}),c.jsx("input",{id:"app-url",type:"url",value:N.url,onChange:z=>xe("url",z.target.value),placeholder:"https://aplicacion.com","aria-invalid":!!E.url,"aria-describedby":E.url?"app-url-error":"app-url-help",disabled:ne})]}),E.url?c.jsx("span",{id:"app-url-error",className:"field-error",role:"alert",tabIndex:-1,children:E.url}):c.jsx("span",{id:"app-url-help",className:"field-help",children:"Si lo dejas vacío, aparecerá como “Enlace pendiente”."})]})]}),c.jsxs("div",{className:"editor-actions",children:[c.jsxs("button",{type:"submit",className:"primary-button",disabled:ne,children:[c.jsx(Ev,{size:18,"aria-hidden":"true"}),ue?"Guardando…":f?"Guardar cambios":"Publicar aplicación"]}),c.jsx("button",{type:"button",className:"secondary-button secondary-button--quiet",disabled:ne,onClick:M,children:"Cancelar"}),f?c.jsxs("button",{type:"button",className:"danger-button",disabled:ne,onClick:()=>{var z;return(z=ve.current)==null?void 0:z.showModal()},children:[c.jsx(Iu,{size:17,"aria-hidden":"true"}),"Eliminar"]}):null]})]}),c.jsxs("aside",{className:"app-preview","aria-label":"Vista previa de la aplicación",children:[c.jsxs("div",{className:"app-preview__label",children:[c.jsx("span",{children:"VISTA PREVIA"}),c.jsx("span",{className:"app-preview__rule"})]}),c.jsxs("div",{className:"preview-card",children:[c.jsx(ti,{app:Je,size:"large",showPlaceholder:!f&&!N.iconDataUrl}),c.jsx("span",{className:"quick-launch__category",children:Je.category}),c.jsx("h3",{children:Je.name}),c.jsx("p",{children:Je.description}),c.jsxs("span",{className:`availability ${Jl(Je.url)?"availability--ready":"availability--pending"}`,children:[c.jsx("span",{"aria-hidden":"true"}),Jl(Je.url)?"Lista para abrir":"Enlace pendiente"]})]}),c.jsx("p",{className:"app-preview__note",children:"Así aparecerá en el Hub para el equipo."})]})]}),c.jsxs("dialog",{className:"confirm-dialog",ref:ve,children:[c.jsx("button",{type:"button",className:"confirm-dialog__close",onClick:()=>{var z;return(z=ve.current)==null?void 0:z.close()},"aria-label":"Cerrar",children:c.jsx(Un,{size:19,"aria-hidden":"true"})}),c.jsx("span",{className:"confirm-dialog__icon",children:c.jsx(Iu,{size:22,"aria-hidden":"true"})}),c.jsx("p",{className:"section-kicker",children:"ACCIÓN PERMANENTE"}),c.jsxs("h2",{children:["Eliminar ",f==null?void 0:f.name]}),c.jsx("p",{children:"La aplicación se quitará del catálogo compartido para todo el equipo. Esta acción no se puede deshacer."}),c.jsxs("div",{className:"confirm-dialog__actions",children:[c.jsx("button",{type:"button",className:"secondary-button",onClick:()=>{var z;return(z=ve.current)==null?void 0:z.close()},children:"Conservar"}),c.jsx("button",{type:"button",className:"danger-button danger-button--solid",onClick:()=>void R(),children:"Sí, eliminar"})]})]}),ee?c.jsx("div",{className:"generation-overlay",role:"dialog","aria-modal":"true","aria-labelledby":"generation-title",children:c.jsxs("div",{className:"generation-overlay__card",children:[c.jsx("span",{className:"generation-spinner","aria-hidden":"true"}),c.jsx("p",{className:"section-kicker",children:"GEMINI"}),c.jsx("h2",{id:"generation-title",children:"Generando el ícono…"}),c.jsx("p",{children:"Estamos creando una propuesta a partir del nombre y la descripción de la aplicación."})]})}):null]})}function b0({data:f,onSave:d,onDelete:v,onBack:r}){const[x,N]=H.useState(null),C=x?f.apps.find(E=>E.id===x)??null:null,w=async(E,G)=>{const B=await d(E,G);return B&&N(B.id),B},U=async E=>{const G=await v(E);return G&&N(null),G};return c.jsxs("main",{className:"page-content admin-page",children:[c.jsx("header",{className:"page-header admin-header",children:c.jsxs("div",{children:[c.jsxs("button",{type:"button",className:"back-button",onClick:r,children:[c.jsx(sv,{size:17,"aria-hidden":"true"}),"Volver al Hub"]}),c.jsx("p",{className:"page-header__meta",children:"PANEL DE ADMINISTRACIÓN"}),c.jsx("h1",{children:"Controla el catálogo."})]})}),c.jsxs("div",{className:"admin-workspace",children:[c.jsx(v0,{apps:f.apps,selectedId:x,onSelect:N,onDelete:U}),c.jsx(g0,{app:C,allApps:f.apps,onSave:w,onDelete:U,onCancel:()=>N(null)})]})]})}function p0(){return c.jsxs("div",{className:"catalog-loading",role:"status","aria-live":"polite",children:[c.jsx(ei,{compact:!0}),c.jsx("span",{className:"generation-spinner","aria-hidden":"true"}),c.jsx("p",{children:"Cargando aplicaciones…"})]})}function S0(){const[f]=H.useState(()=>Yv()),[d,v]=H.useState({schemaVersion:1,apps:[],favoritesByUser:{},recentByUser:{},updatedAt:new Date().toISOString()}),[r,x]=H.useState(null),[N,C]=H.useState(()=>"hub"),[w,U]=H.useState([]),[E,G]=H.useState(!1),[B,J]=H.useState(!1),[ye,Z]=H.useState(""),[ee,V]=H.useState(""),ue=H.useRef(0),Me=H.useRef(null),Te=H.useCallback(Q=>{U(I=>I.filter(S=>S.id!==Q))},[]),ae=H.useCallback((Q,I,S)=>{const g=crypto.randomUUID();U(M=>[...M.slice(-2),{id:g,kind:Q,title:I,detail:S}]),window.setTimeout(()=>{U(M=>M.filter(R=>R.id!==g))},5200)},[]);H.useEffect(()=>{let Q=!0;const I=M=>{const R=(M.expires_at??Math.floor(Date.now()/1e3)+3600)*1e3;x(z=>!z||z.user.id!==M.user.id||z.expiresAt===R?z:{...z,expiresAt:R})},S=async M=>{const R=++ue.current;if(!M){if(!Q||R!==ue.current)return;Me.current=null,x(null),J(!1),G(!0);return}if(Me.current===M.user.id){if(!Q||R!==ue.current)return;I(M),G(!0);return}const z=await l0(M);if(!Q||R!==ue.current)return;if(!z.session){Me.current=null,x(null),V(z.error),J(!1),G(!0),await qe.auth.signOut({scope:"local"});return}const o=z.session;Me.current=o.user.id,V(""),x(o),G(!0),window.location.hash!=="#hub"&&window.location.hash!=="#admin"&&window.history.replaceState(null,"",Ns("#hub"))};qe.auth.getSession().then(({data:{session:M}})=>{S(M)});const{data:{subscription:g}}=qe.auth.onAuthStateChange((M,R)=>{if(M!=="INITIAL_SESSION"){if(M==="TOKEN_REFRESHED"&&R&&Me.current===R.user.id){I(R);return}window.setTimeout(()=>{S(R)},0)}});return()=>{Q=!1,g.unsubscribe()}},[]);const ve=(r==null?void 0:r.user)??null;H.useEffect(()=>{if(!ve)return;let Q=!0,I=!1;const S=async()=>{if(!I){I=!0;try{const R=await Hs();if(!Q)return;v(z=>({...z,apps:R,updatedAt:new Date().toISOString()}))}catch(R){console.error("No se pudo refrescar el catálogo compartido:",R)}finally{I=!1}}};(async()=>{J(!1),Z("");try{const R=await Wv(ve,f.data.apps),z=await Kv(ve.id);if(!Q)return;v(z),J(!0),R>0&&ae("success","Catálogo compartido activado",`${R} ${R===1?"aplicación fue migrada":"aplicaciones fueron migradas"} a Supabase.`)}catch(R){if(!Q)return;const z=R instanceof Error?R.message:"No se pudo cargar el catálogo compartido.";Z(z),J(!0),ae("error","No se pudo cargar el catálogo",z)}})();const M=Fv(()=>void S());return()=>{Q=!1,M()}},[ve==null?void 0:ve.id,ve==null?void 0:ve.role,f.data.apps,ae]),H.useEffect(()=>{const Q=()=>{const I=window.location.hash==="#admin"?"admin":"hub";if(I==="admin"&&(r==null?void 0:r.user.role)!=="admin"){C("hub"),window.history.replaceState(null,"","#hub");return}C(I)};return Q(),window.addEventListener("hashchange",Q),()=>window.removeEventListener("hashchange",Q)},[r==null?void 0:r.user.role]);const ge=Q=>{if(Q==="admin"&&(r==null?void 0:r.user.role)!=="admin"){ae("error","Acceso restringido","Necesitas permisos de administrador para abrir este panel.");return}C(Q),window.history.replaceState(null,"",Ns(Q==="admin"?"#admin":"#hub")),window.scrollTo({top:0,behavior:"smooth"})},W=async Q=>{if(!r)return;const I=r.user.id,S=d.favoritesByUser[I]??[],g=S.includes(Q),M=g?S.filter(R=>R!==Q):[Q,...S];v(R=>({...R,favoritesByUser:{...R.favoritesByUser,[I]:M},updatedAt:new Date().toISOString()}));try{await kv(I,Q,!g),ae("success",g?"Quitada de favoritos":"Agregada a favoritos")}catch(R){v(z=>({...z,favoritesByUser:{...z.favoritesByUser,[I]:S}})),ae("error","No se actualizó el favorito",R instanceof Error?R.message:void 0)}},ne=Q=>{if(!r)return;const I=Jl(Q.url);if(!I){ae("info","Enlace pendiente",`${Q.name} todavía no tiene un enlace configurado.`);return}const S=window.open(I,"_blank","noopener,noreferrer");S&&(S.opener=null);const g=r.user.id,M=d.recentByUser[g]??[],R=[Q.id,...M.filter(z=>z!==Q.id)].slice(0,30);v(z=>({...z,recentByUser:{...z.recentByUser,[g]:R},updatedAt:new Date().toISOString()})),$v(g,Q.id).catch(z=>console.warn("No se guardó el acceso reciente:",z))},Ue=async(Q,I)=>{if(!r||r.user.role!=="admin")return null;const S=I?d.apps.find(g=>g.id===I)??null:null;try{const g=await bm(Q,S,r.user.id);return v(M=>({...M,apps:S?M.apps.map(R=>R.id===g.id?g:R):[g,...M.apps],updatedAt:new Date().toISOString()})),ae("success",S?"Cambios guardados":"Aplicación publicada",`${g.name} ya está disponible en el catálogo compartido.`),g}catch(g){return ae("error","No se guardó la aplicación",g instanceof Error?g.message:void 0),null}},Je=async Q=>{if(!r||r.user.role!=="admin")return!1;const I=d.apps.find(S=>S.id===Q);if(!I)return!1;try{await Jv(I);const S=g=>Object.fromEntries(Object.entries(g).map(([M,R])=>[M,R.filter(z=>z!==Q)]));return v(g=>({...g,apps:g.apps.filter(M=>M.id!==Q),favoritesByUser:S(g.favoritesByUser),recentByUser:S(g.recentByUser),updatedAt:new Date().toISOString()})),ae("success","Aplicación eliminada",`${I.name} salió del catálogo compartido.`),!0}catch(S){return ae("error","No se eliminó la aplicación",S instanceof Error?S.message:void 0),!1}},xe=async Q=>{if(!r||r.user.role==="admin")return!1;if(!tm)return ae("error","No se pudo enviar la solicitud","La función de solicitudes no está disponible en este momento."),!1;const I=new AbortController,S=window.setTimeout(()=>I.abort(),12e4);try{const{data:{session:g}}=await qe.auth.getSession();if(!(g!=null&&g.access_token))throw new Error("La sesión venció. Cierra sesión e inicia nuevamente.");const M=await fetch(tm,{method:"POST",mode:"cors",cache:"no-store",credentials:"omit",signal:I.signal,headers:{Accept:"application/json, text/plain;q=0.9","Content-Type":"text/plain;charset=UTF-8"},body:JSON.stringify({accessToken:g.access_token,appId:Q.id})}),R=await M.text();let z=null;if(R)try{z=JSON.parse(R)}catch{z=null}if(!M.ok||!(z!=null&&z.ok)){const o=String((z==null?void 0:z.error)||"").toLowerCase();let j="No pudimos enviar la solicitud en este momento. Intenta nuevamente.";throw o.includes("pendiente")?j="Ya tienes una solicitud pendiente para esta aplicación.":o.includes("sesión")||M.status===401?j="Tu sesión venció. Cierra sesión e inicia nuevamente.":o.includes("ya no está publicada")||o.includes("no está disponible")?j="La aplicación seleccionada ya no está disponible.":(o.includes("no está autorizado")||M.status===403)&&(j="Tu cuenta no está habilitada para enviar esta solicitud."),console.error("Error interno al enviar la solicitud de acceso",{status:M.status,responseText:R}),new Error(j)}return ae("success","Solicitud enviada",z.message||`La solicitud para ${Q.name} fue enviada correctamente.`),!0}catch(g){return g instanceof DOMException&&g.name==="AbortError"?ae("error","La solicitud tardó demasiado","Intenta nuevamente en unos minutos."):g instanceof TypeError&&/failed to fetch/i.test(g.message)?(console.error("No se pudo contactar el servicio de solicitudes",g),ae("error","No se pudo enviar la solicitud","Intenta nuevamente en unos minutos.")):ae("error","No se pudo enviar la solicitud",g instanceof Error?g.message:"Intenta nuevamente en unos minutos."),!1}finally{window.clearTimeout(S)}},De=async()=>{V("");const{error:Q}=await qe.auth.signInWithOAuth({provider:"google",options:{redirectTo:Pv(),queryParams:{hd:"gomezleemarketing.com",prompt:"select_account"}}});Q&&(console.error("No se pudo iniciar el acceso con Google:",Q),V("No pudimos abrir el acceso con Google. Intenta nuevamente."))},Xe=async()=>{const{error:Q}=await qe.auth.signOut();if(Q){ae("error","No se pudo cerrar la sesión",Q.message);return}x(null),J(!1),C("hub"),window.history.replaceState(null,"",Ns())};return!E||!r?c.jsxs(c.Fragment,{children:[c.jsx(i0,{onLogin:De,error:ee,disabled:!E}),c.jsx(Ts,{messages:w,onDismiss:Te})]}):B?c.jsxs("div",{className:"app-shell",children:[c.jsx(c0,{user:r.user,view:N,onNavigate:ge,onLogout:Xe}),c.jsx(s0,{user:r.user}),N==="admin"&&r.user.role==="admin"?c.jsx(b0,{data:d,onSave:Ue,onDelete:Je,onBack:()=>ge("hub")}):c.jsx(y0,{user:r.user,data:d,storageWarning:ye,onActivate:ne,onFavorite:Q=>void W(Q),onAdmin:()=>ge("admin"),onRequestAccess:xe}),c.jsx(f0,{user:r.user,view:N,onNavigate:ge,onLogout:Xe}),c.jsx(Ts,{messages:w,onDismiss:Te})]}):c.jsxs(c.Fragment,{children:[c.jsx(p0,{}),c.jsx(Ts,{messages:w,onDismiss:Te})]})}const nm="https://dbit.digitalcompass.agency/storage/v1/object/public/public-assets/GLM.png?v=glm-hub-20260722-v3";function _0(){document.querySelectorAll('link[rel="icon"], link[rel="shortcut icon"]').forEach(v=>{v.remove()});const f=document.createElement("link");f.rel="icon",f.type="image/png",f.sizes="512x512",f.href=nm,document.head.appendChild(f);const d=document.createElement("link");d.rel="shortcut icon",d.type="image/png",d.href=nm,document.head.appendChild(d)}_0();tv.createRoot(document.getElementById("root")).render(c.jsx(H.StrictMode,{children:c.jsx(S0,{})})); diff --git a/dist/glm-favicon.png b/dist/glm-favicon.png new file mode 100644 index 0000000..11eccc8 Binary files /dev/null and b/dist/glm-favicon.png differ diff --git a/dist/glm-logo.png b/dist/glm-logo.png new file mode 100644 index 0000000..75ee2c1 Binary files /dev/null and b/dist/glm-logo.png differ diff --git a/dist/glm-tab-icon-16-20260722.png b/dist/glm-tab-icon-16-20260722.png new file mode 100644 index 0000000..5ecdcf8 Binary files /dev/null and b/dist/glm-tab-icon-16-20260722.png differ diff --git a/dist/glm-tab-icon-180-20260722.png b/dist/glm-tab-icon-180-20260722.png new file mode 100644 index 0000000..860f0e1 Binary files /dev/null and b/dist/glm-tab-icon-180-20260722.png differ diff --git a/dist/glm-tab-icon-20260722.ico b/dist/glm-tab-icon-20260722.ico new file mode 100644 index 0000000..b858eb3 Binary files /dev/null and b/dist/glm-tab-icon-20260722.ico differ diff --git a/dist/glm-tab-icon-32-20260722.png b/dist/glm-tab-icon-32-20260722.png new file mode 100644 index 0000000..e9a3188 Binary files /dev/null and b/dist/glm-tab-icon-32-20260722.png differ diff --git a/dist/glm-tab-icon-64-20260722.png b/dist/glm-tab-icon-64-20260722.png new file mode 100644 index 0000000..a259e24 Binary files /dev/null and b/dist/glm-tab-icon-64-20260722.png differ diff --git a/dist/index.html b/dist/index.html new file mode 100644 index 0000000..625bbea --- /dev/null +++ b/dist/index.html @@ -0,0 +1,19 @@ + + + + + + + + + + + GLM Hub — GomezLee Marketing + + + + +
+ + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..95a9971 --- /dev/null +++ b/index.html @@ -0,0 +1,18 @@ + + + + + + + + + + + GLM Hub — GomezLee Marketing + + +
+ + + + diff --git a/n8n/GLM-Hub-Generar-Icono-Gemini-Nodo-Nativo.json b/n8n/GLM-Hub-Generar-Icono-Gemini-Nodo-Nativo.json new file mode 100644 index 0000000..99f17aa --- /dev/null +++ b/n8n/GLM-Hub-Generar-Icono-Gemini-Nodo-Nativo.json @@ -0,0 +1,464 @@ +{ + "name": "GLM Hub - Generar ícono con Gemini (Nodo nativo)", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "glm-hub-generar-icono", + "responseMode": "responseNode", + "options": { + "allowedOrigins": "*" + } + }, + "id": "47ec047e-250d-4056-a2af-2bb6c5f072b0", + "name": "Webhook - Generar ícono", + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -1120, + 0 + ], + "webhookId": "bc20a398-5ecf-42a2-9d17-9396aa278708" + }, + { + "parameters": { + "jsCode": "const request = $input.first().json ?? {};\nconst rawBody = request.body ?? request;\nlet body = rawBody;\n\nif (typeof rawBody === 'string') {\n try {\n body = JSON.parse(rawBody);\n } catch {\n body = {};\n }\n}\n\nconst headers = request.headers ?? {};\nconst headerAuthorization = String(headers.authorization ?? headers.Authorization ?? '').trim();\nconst accessToken = String(body.accessToken ?? '').trim();\nconst authorization = headerAuthorization.toLowerCase().startsWith('bearer ')\n ? headerAuthorization\n : accessToken\n ? `Bearer ${accessToken}`\n : '';\nconst name = String(body.name ?? '').trim();\nconst description = String(body.description ?? '').trim();\n\nlet error = '';\nif (!authorization.toLowerCase().startsWith('bearer ')) {\n error = 'La sesión de Supabase no fue enviada o no es válida.';\n} else if (name.length < 2 || name.length > 60) {\n error = 'El nombre debe contener entre 2 y 60 caracteres.';\n} else if (!description || description.length > 180) {\n error = 'La descripción debe contener entre 1 y 180 caracteres.';\n}\n\nreturn [{ json: { valid: !error, error, authorization, name, description } }];" + }, + "id": "934fb8be-c5bc-43bd-b44a-53f9db105b32", + "name": "Validar solicitud", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + -860, + 0 + ] + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 2 + }, + "conditions": [ + { + "id": "308c498d-7c3f-4414-a21f-571276aee28b", + "leftValue": "={{ $json.valid }}", + "rightValue": true, + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "id": "3344b885-db2b-473d-a444-a51ca00d063d", + "name": "¿Solicitud válida?", + "type": "n8n-nodes-base.if", + "typeVersion": 2.2, + "position": [ + -640, + 0 + ] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ { ok: false, error: $json.error } }}", + "options": { + "responseCode": 400, + "responseHeaders": { + "entries": [ + { + "name": "Cache-Control", + "value": "no-store" + } + ] + } + } + }, + "id": "af555c88-6ec6-4e0f-9d01-64b6cb16a5c5", + "name": "Responder 400", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.4, + "position": [ + -400, + 180 + ] + }, + { + "parameters": { + "method": "POST", + "url": "={{ ($env.SUPABASE_URL || '').replace(/\\/$/, '') + '/rest/v1/rpc/glm_hub_icon_generation_access' }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "apikey", + "value": "={{ $env.SUPABASE_ANON_KEY }}" + }, + { + "name": "Authorization", + "value": "={{ $('Validar solicitud').item.json.authorization }}" + } + ] + }, + "sendBody": true, + "contentType": "raw", + "rawContentType": "application/json", + "body": "{}", + "options": { + "response": { + "response": { + "neverError": true, + "responseFormat": "json" + } + }, + "timeout": 30000 + } + }, + "id": "42ba23d6-9f3a-4b67-ae48-8253adfb7110", + "name": "Validar administrador en Supabase", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + -400, + -80 + ] + }, + { + "parameters": { + "jsCode": "const permission = $input.first().json ?? {};\nconst request = $('Validar solicitud').first().json;\nconst authorized = permission.authorized === true;\nreturn [{\n json: {\n authorized,\n error: authorized ? '' : 'Tu sesión no tiene permisos de administrador para generar íconos.',\n name: request.name,\n description: request.description,\n email: String(permission.email ?? '')\n }\n}];" + }, + "id": "489c0999-9ca3-4361-8262-18404f5aad35", + "name": "Confirmar autorización", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + -180, + -80 + ] + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 2 + }, + "conditions": [ + { + "id": "c9275da4-0bca-4ce9-bd75-cc7d06e4cf56", + "leftValue": "={{ $json.authorized }}", + "rightValue": true, + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "id": "7c199474-f2b6-4c1c-873d-e578d1f7d500", + "name": "¿Es administrador?", + "type": "n8n-nodes-base.if", + "typeVersion": 2.2, + "position": [ + 40, + -80 + ] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ { ok: false, error: $json.error } }}", + "options": { + "responseCode": 403, + "responseHeaders": { + "entries": [ + { + "name": "Cache-Control", + "value": "no-store" + } + ] + } + } + }, + "id": "1b105633-29f8-4e75-bead-d3022420dfed", + "name": "Responder 403", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.4, + "position": [ + 280, + 100 + ] + }, + { + "parameters": { + "jsCode": "const { name, description } = $input.first().json;\nconst prompt = `Crea un ícono cuadrado 1:1, profesional, minimalista y fácil de reconocer para una aplicación interna de GomezLee Marketing.\n\nNombre de la aplicación: ${name}\nDescripción funcional: ${description}\n\nRequisitos visuales obligatorios:\n- Representa la función principal con un único símbolo simple, claro y reconocible.\n- Estética corporativa moderna, limpia y tecnológica.\n- Paleta inspirada en GLM: azul grisáceo, verde claro y verde azulado.\n- Fondo blanco o transparente.\n- Composición centrada, con buen espacio de respiración.\n- Sin texto, sin letras, sin siglas, sin números, sin marcas comerciales y sin logotipos existentes.\n- Sin mockups, sin fotografías, sin personas, sin sombras exageradas y sin fondos complejos.\n- Debe seguir siendo legible al reducirse a 32 x 32 píxeles.\n- Entrega solamente el ícono final.`;\nreturn [{ json: { prompt } }];" + }, + "id": "1ae24908-c67d-4742-917e-573b69f4ca19", + "name": "Construir prompt del ícono", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 280, + -180 + ] + }, + { + "parameters": { + "resource": "image", + "operation": "generate", + "modelId": { + "__rl": true, + "value": "models/gemini-3-pro-image", + "mode": "list", + "cachedResultName": "models/gemini-3-pro-image (Nano Banana Pro)" + }, + "prompt": "={{ $json.prompt }}", + "options": { + "binaryPropertyOutput": "data" + } + }, + "id": "bdeed7c1-71ba-4af4-9424-b248c06c9e75", + "name": "Generate an image", + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1.2, + "position": [ + 520, + -180 + ], + "onError": "continueErrorOutput" + }, + { + "parameters": { + "respondWith": "binary", + "responseDataSource": "set", + "inputFieldName": "data", + "options": { + "responseCode": 200, + "responseHeaders": { + "entries": [ + { + "name": "Cache-Control", + "value": "no-store" + }, + { + "name": "Content-Disposition", + "value": "inline; filename=\"glm-hub-icon.png\"" + } + ] + } + } + }, + "id": "7349fabd-6d25-48b1-94be-64e36eef5f7e", + "name": "Responder con ícono", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.4, + "position": [ + 800, + -260 + ] + }, + { + "parameters": { + "jsCode": "const input = $input.first().json ?? {};\nconst rawError = input.error ?? input.message ?? input.description ?? input;\nlet message = 'Gemini no pudo generar el ícono. Revisa la credencial, el modelo y la cuota disponible.';\n\nif (typeof rawError === 'string' && rawError.trim()) {\n message = rawError.trim();\n} else if (rawError && typeof rawError === 'object') {\n message = String(rawError.message ?? rawError.description ?? rawError.status ?? message);\n}\n\nreturn [{ json: { ok: false, error: message } }];" + }, + "id": "fc7b61a0-fb00-42a4-bc4a-2e530da36a12", + "name": "Preparar error de Gemini", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 800, + -80 + ] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ { ok: false, error: $json.error } }}", + "options": { + "responseCode": 502, + "responseHeaders": { + "entries": [ + { + "name": "Cache-Control", + "value": "no-store" + } + ] + } + } + }, + "id": "c0bc7fee-a55e-4f40-90b5-f875c41b131c", + "name": "Responder error de Gemini", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.4, + "position": [ + 1040, + -80 + ] + }, + { + "parameters": { + "content": "## CONFIGURACIÓN ÚNICA\n\nEn el nodo **Generate an image**, confirma la credencial existente **Isaac - Gemini Api Pago** después de importar.\n\nEl webhook devuelve la imagen directamente como archivo binario al GLM Hub.", + "height": 220, + "width": 380, + "color": 5 + }, + "id": "fd8a87b4-3924-412b-8d3d-b7707bf6388b", + "name": "Nota de configuración", + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + 420, + 100 + ] + } + ], + "pinData": {}, + "connections": { + "Webhook - Generar ícono": { + "main": [ + [ + { + "node": "Validar solicitud", + "type": "main", + "index": 0 + } + ] + ] + }, + "Validar solicitud": { + "main": [ + [ + { + "node": "¿Solicitud válida?", + "type": "main", + "index": 0 + } + ] + ] + }, + "¿Solicitud válida?": { + "main": [ + [ + { + "node": "Validar administrador en Supabase", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Responder 400", + "type": "main", + "index": 0 + } + ] + ] + }, + "Validar administrador en Supabase": { + "main": [ + [ + { + "node": "Confirmar autorización", + "type": "main", + "index": 0 + } + ] + ] + }, + "Confirmar autorización": { + "main": [ + [ + { + "node": "¿Es administrador?", + "type": "main", + "index": 0 + } + ] + ] + }, + "¿Es administrador?": { + "main": [ + [ + { + "node": "Construir prompt del ícono", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Responder 403", + "type": "main", + "index": 0 + } + ] + ] + }, + "Construir prompt del ícono": { + "main": [ + [ + { + "node": "Generate an image", + "type": "main", + "index": 0 + } + ] + ] + }, + "Generate an image": { + "main": [ + [ + { + "node": "Responder con ícono", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Preparar error de Gemini", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar error de Gemini": { + "main": [ + [ + { + "node": "Responder error de Gemini", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1", + "saveManualExecutions": true, + "callerPolicy": "workflowsFromSameOwner" + }, + "versionId": "ae79a50a-79d1-43f8-b856-b4106bae3e60", + "meta": { + "templateCredsSetupCompleted": false + }, + "tags": [] +} diff --git a/n8n/GLM-Hub-Solicitudes-Acceso-Aprobacion.json b/n8n/GLM-Hub-Solicitudes-Acceso-Aprobacion.json new file mode 100644 index 0000000..679b202 --- /dev/null +++ b/n8n/GLM-Hub-Solicitudes-Acceso-Aprobacion.json @@ -0,0 +1,933 @@ +{ + "name": "GLM Hub - Solicitudes de acceso y aprobación", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "glm-hub-solicitar-acceso", + "responseMode": "responseNode", + "options": { + "allowedOrigins": "*" + } + }, + "id": "c1e25778-7556-4c87-9069-560c848cf185", + "name": "Webhook - Solicitar acceso", + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -240, + 368 + ], + "webhookId": "ad3e98b9-baf2-400f-90e7-dcd4c5c20457" + }, + { + "parameters": { + "jsCode": "const source = $json.body ?? $json;\nlet payload = source;\nif (typeof payload === 'string') {\n try { payload = JSON.parse(payload); } catch { payload = {}; }\n}\nconst accessToken = String(payload?.accessToken ?? '').trim();\nconst appId = String(payload?.appId ?? '').trim();\nconst jwtPattern = /^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$/;\nconst uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\nlet error = '';\nif (!jwtPattern.test(accessToken)) error = 'La sesión recibida no es válida.';\nelse if (!uuidPattern.test(appId)) error = 'Selecciona una aplicación válida.';\nreturn [{ json: { valid: !error, error, authorization: `Bearer ${accessToken}`, appId } }];" + }, + "id": "382d40fd-c718-4ca5-be37-33fe80ad0483", + "name": "Validar solicitud de acceso", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + -16, + 368 + ] + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 2 + }, + "conditions": [ + { + "id": "b5933422-7722-4c98-b34b-262a20d3ac05", + "leftValue": "={{ $json.valid }}", + "rightValue": true, + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "id": "edbdd398-aa28-46b5-8fdd-d359e9d8dcbd", + "name": "¿Solicitud de acceso válida?", + "type": "n8n-nodes-base.if", + "typeVersion": 2.2, + "position": [ + 208, + 368 + ] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ { ok: false, error: $json.error } }}", + "options": { + "responseCode": 400, + "responseHeaders": { + "entries": [ + { + "name": "Cache-Control", + "value": "no-store" + } + ] + } + } + }, + "id": "81e1ad6e-ca84-4c3f-9897-bdfcab58e965", + "name": "Responder solicitud inválida", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.4, + "position": [ + 432, + 528 + ] + }, + { + "parameters": { + "method": "POST", + "url": "https://dbit.digitalcompass.agency/rest/v1/rpc/glm_hub_prepare_access_request", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "apikey", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE" + }, + { + "name": "Authorization", + "value": "={{ $('Validar solicitud de acceso').first().json.authorization }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "contentType": "raw", + "rawContentType": "application/json", + "body": "={{ JSON.stringify({ p_app_id: $json.appId }) }}", + "options": { + "response": { + "response": { + "neverError": true, + "responseFormat": "json", + "fullResponse": true + } + }, + "timeout": 30000 + } + }, + "id": "513d8635-0e4b-49bf-beeb-1fcd76555b9d", + "name": "Crear solicitud en Supabase", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 432, + 288 + ] + }, + { + "parameters": { + "jsCode": "const source = $input.first()?.json ?? {};\nconst parseJson = (value) => {\n if (typeof value !== 'string') return value;\n try { return JSON.parse(value); } catch { return value; }\n};\nlet payload = source.body ?? source.data ?? source.response?.body ?? source;\npayload = parseJson(payload);\nif (Array.isArray(payload)) payload = payload[0] ?? {};\nif (payload && typeof payload === 'object' && payload.body !== undefined && payload.ok === undefined) {\n payload = parseJson(payload.body);\n if (Array.isArray(payload)) payload = payload[0] ?? {};\n}\nif (payload?.ok !== true) {\n return [{ json: {\n created: false,\n publicError: 'La solicitud fue registrada, pero no se pudo enviar el correo. Intenta nuevamente en unos minutos.',\n technicalError: String(payload?.error ?? payload?.message ?? 'No se pudieron emitir los enlaces de aprobación.')\n } }];\n}\nconst escapeHtml = (value) => String(value ?? '').replace(/[&<>\"']/g, (c) => ({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));\nconst encode = encodeURIComponent;\nconst confirmBase = 'https://agenteit.digitalcompass.agency/webhook/glm-hub-confirmar-solicitud';\nconst requesterName = String(payload.requesterName ?? 'Usuario GLM');\nconst requesterEmail = String(payload.requesterEmail ?? '');\nconst appName = String(payload.appName ?? 'Aplicación GLM');\nconst appCategory = String(payload.appCategory ?? '');\nconst requestId = String(payload.requestId ?? '');\nconst approvers = Array.isArray(payload.approvers) ? payload.approvers : [];\nif (approvers.length !== 3) {\n return [{ json: {\n created: false,\n publicError: 'No se pudo completar el envío de la solicitud. Intenta nuevamente en unos minutos.',\n technicalError: `Se esperaban 3 aprobadores y se recibieron ${approvers.length}.`\n } }];\n}\nreturn approvers.map((approver) => {\n const approveUrl = `${confirmBase}?token=${encode(String(approver.token ?? ''))}&decision=approved&app=${encode(appName)}`;\n const rejectUrl = `${confirmBase}?token=${encode(String(approver.token ?? ''))}&decision=rejected&app=${encode(appName)}`;\n const html = `
\"GomezLee
GLM HUB · SOLICITUD DE ACCESO

Una persona necesita acceso.

Hola ${escapeHtml(approver.name)}, revisa la siguiente solicitud.

Solicitante${escapeHtml(requesterName)}
Correo${escapeHtml(requesterEmail)}
Aplicación${escapeHtml(appName)}
Área${escapeHtml(appCategory)}

Por seguridad, cada botón abre una pantalla de confirmación. La primera decisión confirmada será definitiva para los tres destinatarios.

Aceptar solicitudRechazar solicitud
Solicitud ${escapeHtml(requestId)} · GomezLee Marketing · Uso interno
`;\n return { json: { created: true, requestId, sendTo: String(approver.email ?? ''), subject: `Solicitud de acceso | ${appName} | ${requesterName}`, html } };\n});" + }, + "id": "5cf9121e-d2e8-4975-a833-a426c77d9389", + "name": "Preparar correos de aprobación", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1328, + 208 + ] + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 2 + }, + "conditions": [ + { + "id": "02a636e0-63a0-4a56-b108-65029e555646", + "leftValue": "={{ $json.created }}", + "rightValue": true, + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "id": "ee5ecbd9-95ab-42f2-a013-33eff66be5ea", + "name": "¿Solicitud registrada?", + "type": "n8n-nodes-base.if", + "typeVersion": 2.2, + "position": [ + 1568, + 208 + ] + }, + { + "parameters": { + "sendTo": "=iaracena@gomezleemarketing.com", + "subject": "={{ $json.subject }}", + "message": "={{ $json.html }}", + "options": { + "appendAttribution": false, + "senderName": "GLM Hub" + } + }, + "id": "6fafb4fa-b7bc-46ed-b435-63cbd6c85538", + "name": "Enviar solicitud a aprobadores", + "type": "n8n-nodes-base.gmail", + "typeVersion": 2.1, + "position": [ + 1808, + 112 + ], + "webhookId": "7dc65e7b-4fb6-4a2f-b685-34171dd9ce1a", + "credentials": { + "gmailOAuth2": { + "id": "UDcO1FLJqA453V2D", + "name": "Gmail account 3" + } + } + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ { ok: true, message: 'La solicitud fue enviada a Isaac Aracena, José Leopoldo Gómez y Máximo Gómez.', requestId: $('Preparar correos de aprobación').first().json.requestId } }}", + "options": { + "responseCode": 200, + "responseHeaders": { + "entries": [ + { + "name": "Cache-Control", + "value": "no-store" + } + ] + } + } + }, + "id": "2aea248a-8970-4439-9c5b-18800257d835", + "name": "Responder solicitud enviada", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.4, + "position": [ + 2048, + 112 + ] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ { ok: false, error: $json.publicError || 'No pudimos enviar la solicitud. Intenta nuevamente.' } }}", + "options": { + "responseCode": 409, + "responseHeaders": { + "entries": [ + { + "name": "Cache-Control", + "value": "no-store" + } + ] + } + } + }, + "id": "b2affc40-34dd-47a5-ae2c-c2f9e8bb4fe6", + "name": "Responder error al registrar", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.4, + "position": [ + 1808, + 320 + ] + }, + { + "parameters": { + "path": "glm-hub-confirmar-solicitud", + "responseMode": "responseNode", + "options": {} + }, + "id": "d1276943-026e-414e-92eb-d9665fce1aa4", + "name": "Webhook - Confirmar decisión", + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -240, + 832 + ], + "webhookId": "cba1c38c-37bc-4acb-bd61-48035604a5c4" + }, + { + "parameters": { + "jsCode": "const query = $json.query ?? {};\nconst token = String(query.token ?? '').trim();\nconst decision = String(query.decision ?? '').trim().toLowerCase();\nconst appName = String(query.app ?? 'aplicación GLM').trim();\nconst valid = /^[0-9a-f]{64}$/i.test(token) && ['approved','rejected'].includes(decision);\nconst escapeHtml = (value) => String(value ?? '').replace(/[&<>\"']/g, (c) => ({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));\nconst actionUrl = 'https://agenteit.digitalcompass.agency/webhook/glm-hub-resolver-solicitud';\nconst approved = decision === 'approved';\nconst title = approved ? 'Confirmar aprobación' : 'Confirmar rechazo';\nconst actionLabel = approved ? 'Sí, aprobar solicitud' : 'Sí, rechazar solicitud';\nconst actionColor = approved ? '#6CC24A' : '#E87722';\nconst html = valid\n ? `${title} · GLM Hub
\"GomezLee

GLM HUB · DECISIÓN DE ACCESO

${title}

Vas a ${approved ? 'aprobar' : 'rechazar'} la solicitud de acceso a ${escapeHtml(appName)}. La decisión será definitiva y los otros enlaces quedarán sin efecto.

Cierra esta pestaña para cancelar sin registrar ninguna decisión.

`\n : `
\"GomezLee

Enlace no válido

El enlace está incompleto o no corresponde a una solicitud de GLM Hub.

`;\nreturn [{ json: { html } }];" + }, + "id": "84f519ec-c313-42ae-ba6c-7fb612b7b510", + "name": "Construir página de confirmación", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + -16, + 832 + ] + }, + { + "parameters": { + "respondWith": "text", + "responseBody": "={{ $json.html }}", + "options": { + "responseCode": 200, + "responseHeaders": { + "entries": [ + { + "name": "Content-Type", + "value": "text/html; charset=utf-8" + }, + { + "name": "Cache-Control", + "value": "no-store" + }, + { + "name": "X-Frame-Options", + "value": "DENY" + } + ] + } + } + }, + "id": "9fab6a61-b360-4bb9-9b07-6c8244ba40db", + "name": "Mostrar confirmación", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.4, + "position": [ + 208, + 832 + ] + }, + { + "parameters": { + "httpMethod": "POST", + "path": "glm-hub-resolver-solicitud", + "responseMode": "responseNode", + "options": {} + }, + "id": "95a17231-e0aa-4004-9d0e-dd581488b524", + "name": "Webhook - Resolver solicitud", + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -240, + 1200 + ], + "webhookId": "54eeee1b-b24b-46ae-84dc-70c4103e3a3a" + }, + { + "parameters": { + "jsCode": "const source = $json.body ?? $json;\nlet payload = source;\nif (typeof payload === 'string') {\n try {\n payload = Object.fromEntries(new URLSearchParams(payload));\n } catch { payload = {}; }\n}\nconst token = String(payload?.token ?? '').trim();\nconst decision = String(payload?.decision ?? '').trim().toLowerCase();\nlet error = '';\nif (!/^[0-9a-f]{64}$/i.test(token)) error = 'El enlace de decisión no es válido.';\nelse if (!['approved','rejected'].includes(decision)) error = 'La decisión recibida no es válida.';\nreturn [{ json: { valid: !error, error, token, decision } }];" + }, + "id": "87155b04-5944-43ae-beff-9310138b2863", + "name": "Validar decisión", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + -16, + 1200 + ] + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 2 + }, + "conditions": [ + { + "id": "5c42c35a-903b-4a18-a7ef-aab6e6bb5c26", + "leftValue": "={{ $json.valid }}", + "rightValue": true, + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "id": "876e85ab-fbcd-4090-a5a4-31daf40711f5", + "name": "¿Decisión válida?", + "type": "n8n-nodes-base.if", + "typeVersion": 2.2, + "position": [ + 208, + 1200 + ] + }, + { + "parameters": { + "jsCode": "const message = String($json.error ?? 'El enlace de decisión no es válido.');\nconst escapeHtml = (value) => String(value ?? '').replace(/[&<>\"']/g, (c) => ({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));\nconst html = `
\"GomezLee

No se pudo procesar

${escapeHtml(message)}

`;\nreturn [{ json: { html } }];" + }, + "id": "c0527ef0-615e-463d-9e54-f7a942e02421", + "name": "Preparar decisión inválida", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 432, + 1392 + ] + }, + { + "parameters": { + "respondWith": "text", + "responseBody": "={{ $json.html }}", + "options": { + "responseCode": 400, + "responseHeaders": { + "entries": [ + { + "name": "Content-Type", + "value": "text/html; charset=utf-8" + }, + { + "name": "Cache-Control", + "value": "no-store" + }, + { + "name": "X-Frame-Options", + "value": "DENY" + } + ] + } + } + }, + "id": "88bb2617-4e75-4f99-9aca-7c06bb28c86e", + "name": "Responder decisión inválida", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.4, + "position": [ + 672, + 1392 + ] + }, + { + "parameters": { + "method": "POST", + "url": "https://dbit.digitalcompass.agency/rest/v1/rpc/glm_hub_decide_access_request", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "apikey", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" + }, + { + "name": "Authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" + } + ] + }, + "sendBody": true, + "contentType": "raw", + "rawContentType": "application/json", + "body": "={{ JSON.stringify({ p_token: $json.token, p_decision: $json.decision }) }}", + "options": { + "response": { + "response": { + "neverError": true, + "responseFormat": "json" + } + }, + "timeout": 30000 + } + }, + "id": "675cfecd-8a18-4c75-8e3f-aebb79390f34", + "name": "Registrar decisión en Supabase", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 432, + 1120 + ] + }, + { + "parameters": { + "jsCode": "const source = $input.first()?.json ?? {};\nlet payload = source.body ?? source;\nif (typeof payload === 'string') { try { payload = JSON.parse(payload); } catch { payload = {}; } }\nif (Array.isArray(payload)) payload = payload[0] ?? {};\nconst escapeHtml = (value) => String(value ?? '').replace(/[&<>\"']/g, (c) => ({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));\nconst status = String(payload.status ?? '');\nconst approved = status === 'approved';\nconst statusLabel = approved ? 'APROBADA' : status === 'rejected' ? 'RECHAZADA' : 'SIN PROCESAR';\nconst statusColor = approved ? '#6CC24A' : '#E87722';\nconst ok = payload.ok === true;\nconst alreadyDecided = payload.alreadyDecided === true;\nconst requesterName = String(payload.requesterName ?? '');\nconst requesterEmail = String(payload.requesterEmail ?? '');\nconst appName = String(payload.appName ?? 'Aplicación GLM');\nconst decidedByName = String(payload.decidedByName ?? '');\nconst decidedByEmail = String(payload.decidedByEmail ?? '');\nconst responseTitle = ok ? (approved ? 'Solicitud aprobada' : 'Solicitud rechazada') : alreadyDecided ? 'Solicitud ya atendida' : 'No se pudo procesar';\nconst responseMessage = ok\n ? `La decisión fue registrada. IT Support recibirá los datos para continuar con el acceso.`\n : alreadyDecided\n ? `Esta solicitud ya fue ${status === 'approved' ? 'aprobada' : 'rechazada'} por ${escapeHtml(decidedByName || decidedByEmail || 'otra persona')}. No se realizó ningún cambio adicional.`\n : escapeHtml(payload.error ?? 'El enlace no pudo ser validado.');\nconst responseHtml = `${responseTitle} · GLM Hub
\"GomezLee

GLM HUB · SOLICITUD DE ACCESO

${responseTitle}

${responseMessage}

Ya puedes cerrar esta pestaña.

`;\nconst itHtml = ok ? `
\"GomezLee
GLM HUB · DECISIÓN REGISTRADA

Solicitud ${statusLabel.toLowerCase()}.

IT Support debe continuar según la decisión indicada.

${statusLabel}
Persona solicitante${escapeHtml(requesterName)}
Correo solicitante${escapeHtml(requesterEmail)}
Aplicación${escapeHtml(appName)}
Decisión${statusLabel}
Decidido por${escapeHtml(decidedByName)}
Correo de quien decidió${escapeHtml(decidedByEmail)}
GomezLee Marketing · GLM Hub · Uso interno
` : '';\nreturn [{ json: { notifyIt: ok, responseHtml, itHtml, subject: `Acceso ${approved ? 'aprobado' : 'rechazado'} | ${appName} | ${requesterName}` } }];" + }, + "id": "2c8fb533-fcc0-4958-a210-9498c55d98d2", + "name": "Preparar resultado de decisión", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 672, + 1120 + ] + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 2 + }, + "conditions": [ + { + "id": "2ef854a5-d270-49bf-9a6a-5bb92aeab434", + "leftValue": "={{ $json.notifyIt }}", + "rightValue": true, + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "id": "993d5ec8-1187-4e92-adce-8c9b41c08a91", + "name": "¿Notificar a IT Support?", + "type": "n8n-nodes-base.if", + "typeVersion": 2.2, + "position": [ + 912, + 1120 + ] + }, + { + "parameters": { + "sendTo": "iaracena@gomezleemarketing.com", + "subject": "={{ $json.subject }}", + "message": "={{ $json.itHtml }}", + "options": { + "appendAttribution": false, + "senderName": "GLM Hub" + } + }, + "id": "c9fbf481-045c-4f2b-80bd-2ad006b0af56", + "name": "Enviar decisión a IT Support", + "type": "n8n-nodes-base.gmail", + "typeVersion": 2.1, + "position": [ + 1152, + 1024 + ], + "webhookId": "234c8e3b-b904-41b8-8a74-9ca835f759df", + "credentials": { + "gmailOAuth2": { + "id": "UDcO1FLJqA453V2D", + "name": "Gmail account 3" + } + } + }, + { + "parameters": { + "respondWith": "text", + "responseBody": "={{ $('Preparar resultado de decisión').first().json.responseHtml }}", + "options": { + "responseCode": 200, + "responseHeaders": { + "entries": [ + { + "name": "Content-Type", + "value": "text/html; charset=utf-8" + }, + { + "name": "Cache-Control", + "value": "no-store" + }, + { + "name": "X-Frame-Options", + "value": "DENY" + } + ] + } + } + }, + "id": "d16bcca2-1d7c-44e0-be65-658478a2623f", + "name": "Mostrar resultado final", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.4, + "position": [ + 1392, + 1120 + ] + }, + { + "parameters": { + "jsCode": "const source = $input.first()?.json ?? {};\n\nconst parseJson = (value) => {\n if (typeof value !== 'string') return value;\n try { return JSON.parse(value); } catch { return value; }\n};\n\nlet payload = source.body ?? source.data ?? source.response?.body ?? source;\npayload = parseJson(payload);\nif (Array.isArray(payload)) payload = payload[0] ?? {};\nif (payload && typeof payload === 'object' && payload.body !== undefined && payload.ok === undefined) {\n payload = parseJson(payload.body);\n if (Array.isArray(payload)) payload = payload[0] ?? {};\n}\n\nconst statusCode = Number(source.statusCode ?? source.status ?? source.response?.statusCode ?? 200);\nconst rawError = String(payload?.error ?? payload?.message ?? source?.message ?? '').trim();\nconst normalized = rawError.toLowerCase();\nconst code = String(payload?.code ?? '').toUpperCase();\n\nlet publicError = 'No pudimos registrar la solicitud. Intenta nuevamente.';\nif (code === 'PENDING_EXISTS' || normalized.includes('pendiente')) {\n publicError = 'Ya tienes una solicitud pendiente para esta aplicación.';\n} else if (normalized.includes('sesión') || statusCode === 401) {\n publicError = 'Tu sesión venció. Cierra sesión e inicia nuevamente.';\n} else if (normalized.includes('ya no está publicada') || normalized.includes('no está publicada')) {\n publicError = 'La aplicación seleccionada ya no está disponible.';\n} else if (normalized.includes('no está autorizado') || statusCode === 403) {\n publicError = 'Tu cuenta no está habilitada para enviar esta solicitud.';\n} else if (normalized.includes('administradores no necesitan')) {\n publicError = 'Tu cuenta administrativa no necesita solicitar acceso desde el Hub.';\n}\n\nreturn [{ json: {\n registered: payload?.ok === true && statusCode >= 200 && statusCode < 300,\n publicError,\n technicalError: rawError || `Respuesta HTTP ${statusCode} sin detalle`,\n statusCode,\n requestId: String(payload?.requestId ?? '')\n} }];" + }, + "id": "1f46de7c-f14a-44b1-8852-cf1a9d4d6bd5", + "name": "Confirmar registro de solicitud", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 640, + 288 + ] + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 2 + }, + "conditions": [ + { + "id": "aa091e1c-09f8-4cdb-b8ac-3cbc4a7a33c8", + "leftValue": "={{ $json.registered }}", + "rightValue": true, + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "id": "0b0d9f6a-2d91-4d8f-b4f8-b20111a4d136", + "name": "¿Solicitud registrada en Supabase?", + "type": "n8n-nodes-base.if", + "typeVersion": 2.2, + "position": [ + 864, + 288 + ] + }, + { + "parameters": { + "method": "POST", + "url": "https://dbit.digitalcompass.agency/rest/v1/rpc/glm_hub_issue_access_request_tokens", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "apikey", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" + }, + { + "name": "Authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" + } + ] + }, + "sendBody": true, + "contentType": "raw", + "rawContentType": "application/json", + "body": "={{ JSON.stringify({ p_request_id: $json.requestId }) }}", + "options": { + "response": { + "response": { + "neverError": true, + "responseFormat": "json", + "fullResponse": true + } + }, + "timeout": 30000 + } + }, + "id": "9203157e-c9e7-4ad3-bb35-ae683c594f6b", + "name": "Emitir enlaces de aprobación", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1088, + 208 + ] + } + ], + "connections": { + "Webhook - Solicitar acceso": { + "main": [ + [ + { + "node": "Validar solicitud de acceso", + "type": "main", + "index": 0 + } + ] + ] + }, + "Validar solicitud de acceso": { + "main": [ + [ + { + "node": "¿Solicitud de acceso válida?", + "type": "main", + "index": 0 + } + ] + ] + }, + "¿Solicitud de acceso válida?": { + "main": [ + [ + { + "node": "Crear solicitud en Supabase", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Responder solicitud inválida", + "type": "main", + "index": 0 + } + ] + ] + }, + "Crear solicitud en Supabase": { + "main": [ + [ + { + "node": "Confirmar registro de solicitud", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar correos de aprobación": { + "main": [ + [ + { + "node": "¿Solicitud registrada?", + "type": "main", + "index": 0 + } + ] + ] + }, + "¿Solicitud registrada?": { + "main": [ + [ + { + "node": "Enviar solicitud a aprobadores", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Responder error al registrar", + "type": "main", + "index": 0 + } + ] + ] + }, + "Enviar solicitud a aprobadores": { + "main": [ + [ + { + "node": "Responder solicitud enviada", + "type": "main", + "index": 0 + } + ] + ] + }, + "Webhook - Confirmar decisión": { + "main": [ + [ + { + "node": "Construir página de confirmación", + "type": "main", + "index": 0 + } + ] + ] + }, + "Construir página de confirmación": { + "main": [ + [ + { + "node": "Mostrar confirmación", + "type": "main", + "index": 0 + } + ] + ] + }, + "Webhook - Resolver solicitud": { + "main": [ + [ + { + "node": "Validar decisión", + "type": "main", + "index": 0 + } + ] + ] + }, + "Validar decisión": { + "main": [ + [ + { + "node": "¿Decisión válida?", + "type": "main", + "index": 0 + } + ] + ] + }, + "¿Decisión válida?": { + "main": [ + [ + { + "node": "Registrar decisión en Supabase", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Preparar decisión inválida", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar decisión inválida": { + "main": [ + [ + { + "node": "Responder decisión inválida", + "type": "main", + "index": 0 + } + ] + ] + }, + "Registrar decisión en Supabase": { + "main": [ + [ + { + "node": "Preparar resultado de decisión", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar resultado de decisión": { + "main": [ + [ + { + "node": "¿Notificar a IT Support?", + "type": "main", + "index": 0 + } + ] + ] + }, + "¿Notificar a IT Support?": { + "main": [ + [ + { + "node": "Enviar decisión a IT Support", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Mostrar resultado final", + "type": "main", + "index": 0 + } + ] + ] + }, + "Enviar decisión a IT Support": { + "main": [ + [ + { + "node": "Mostrar resultado final", + "type": "main", + "index": 0 + } + ] + ] + }, + "Confirmar registro de solicitud": { + "main": [ + [ + { + "node": "¿Solicitud registrada en Supabase?", + "type": "main", + "index": 0 + } + ] + ] + }, + "¿Solicitud registrada en Supabase?": { + "main": [ + [ + { + "node": "Emitir enlaces de aprobación", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Responder error al registrar", + "type": "main", + "index": 0 + } + ] + ] + }, + "Emitir enlaces de aprobación": { + "main": [ + [ + { + "node": "Preparar correos de aprobación", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": true, + "settings": { + "executionOrder": "v1", + "binaryMode": "separate" + }, + "tags": [] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f342ef2 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1872 @@ +{ + "name": "glm-app-hub", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "glm-app-hub", + "version": "1.0.0", + "dependencies": { + "lucide-react": "0.575.0", + "react": "19.2.7", + "react-dom": "19.2.7" + }, + "devDependencies": { + "@types/react": "19.2.17", + "@types/react-dom": "19.2.3", + "@vitejs/plugin-react": "5.2.0", + "typescript": "5.9.3", + "vite": "6.4.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", + "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.392", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.392.tgz", + "integrity": "sha512-1yQq3VQCZRwsnYc67Oc+1fge6Lwtn0hzi6zmEVkB61Zx21kTbwJAW4dFLadl5Rc1tKhG/kSpYXnfiAhu0f0a1g==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.575.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.575.0.tgz", + "integrity": "sha512-VuXgKZrk0uiDlWjGGXmKV6MSk9Yy4l10qgVvzGn2AWBx1Ylt0iBexKOAoA6I7JO3m+M9oeovJd3yYENfkUbOeg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..33f3ff3 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "glm-app-hub", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc --noEmit && vite build", + "preview": "vite preview", + "typecheck": "tsc --noEmit --pretty false" + }, + "dependencies": { + "lucide-react": "0.575.0", + "react": "19.2.7", + "react-dom": "19.2.7" + }, + "devDependencies": { + "@types/react": "19.2.17", + "@types/react-dom": "19.2.3", + "@vitejs/plugin-react": "5.2.0", + "typescript": "5.9.3", + "vite": "6.4.3" + } +} diff --git a/public/glm-favicon.png b/public/glm-favicon.png new file mode 100644 index 0000000..11eccc8 Binary files /dev/null and b/public/glm-favicon.png differ diff --git a/public/glm-logo.png b/public/glm-logo.png new file mode 100644 index 0000000..75ee2c1 Binary files /dev/null and b/public/glm-logo.png differ diff --git a/public/glm-tab-icon-16-20260722.png b/public/glm-tab-icon-16-20260722.png new file mode 100644 index 0000000..5ecdcf8 Binary files /dev/null and b/public/glm-tab-icon-16-20260722.png differ diff --git a/public/glm-tab-icon-180-20260722.png b/public/glm-tab-icon-180-20260722.png new file mode 100644 index 0000000..860f0e1 Binary files /dev/null and b/public/glm-tab-icon-180-20260722.png differ diff --git a/public/glm-tab-icon-20260722.ico b/public/glm-tab-icon-20260722.ico new file mode 100644 index 0000000..b858eb3 Binary files /dev/null and b/public/glm-tab-icon-20260722.ico differ diff --git a/public/glm-tab-icon-32-20260722.png b/public/glm-tab-icon-32-20260722.png new file mode 100644 index 0000000..e9a3188 Binary files /dev/null and b/public/glm-tab-icon-32-20260722.png differ diff --git a/public/glm-tab-icon-64-20260722.png b/public/glm-tab-icon-64-20260722.png new file mode 100644 index 0000000..a259e24 Binary files /dev/null and b/public/glm-tab-icon-64-20260722.png differ diff --git a/scripts/build-static-esm.mjs b/scripts/build-static-esm.mjs new file mode 100644 index 0000000..93dccb5 --- /dev/null +++ b/scripts/build-static-esm.mjs @@ -0,0 +1,98 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import ts from '../node_modules/typescript/lib/typescript.js'; + +const root = path.resolve(new URL('..', import.meta.url).pathname); +const srcDir = path.join(root, 'src'); +const distDir = path.join(root, 'dist'); +const assetsDir = path.join(distDir, 'assets'); + +const env = { + VITE_SUPABASE_URL: 'https://dbit.digitalcompass.agency', + VITE_SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE', + VITE_ICON_GENERATOR_WEBHOOK_URL: 'https://agenteit.digitalcompass.agency/webhook/glm-hub-generar-icono', + VITE_ACCESS_REQUEST_WEBHOOK_URL: 'https://agenteit.digitalcompass.agency/webhook/glm-hub-solicitar-acceso', + BASE_URL: '/', +}; + +function replaceEnv(source) { + return source + .replaceAll('import.meta.env.VITE_SUPABASE_URL', JSON.stringify(env.VITE_SUPABASE_URL)) + .replaceAll('import.meta.env.VITE_SUPABASE_ANON_KEY', JSON.stringify(env.VITE_SUPABASE_ANON_KEY)) + .replaceAll('import.meta.env.VITE_ICON_GENERATOR_WEBHOOK_URL', JSON.stringify(env.VITE_ICON_GENERATOR_WEBHOOK_URL)) + .replaceAll('import.meta.env.VITE_ACCESS_REQUEST_WEBHOOK_URL', JSON.stringify(env.VITE_ACCESS_REQUEST_WEBHOOK_URL)) + .replaceAll('import.meta.env.BASE_URL', JSON.stringify(env.BASE_URL)); +} + +function fixLocalImports(code) { + return code + .replace(/^import\s+["']\.\/styles\.css["'];?\s*$/gm, '') + .replace(/(from\s+["'])(\.\.?\/[^"']+)(["'])/g, (_m, start, spec, end) => { + if (/\.(?:js|mjs|cjs|json|css)$/.test(spec)) return `${start}${spec}${end}`; + return `${start}${spec}.js${end}`; + }); +} + +fs.rmSync(distDir, { recursive: true, force: true }); +fs.mkdirSync(assetsDir, { recursive: true }); + +const sourceFiles = ['main.tsx', 'App.tsx', 'data.ts', 'repository.ts', 'storage.ts', 'supabase.ts', 'types.ts']; +for (const file of sourceFiles) { + const sourcePath = path.join(srcDir, file); + const source = replaceEnv(fs.readFileSync(sourcePath, 'utf8')); + const result = ts.transpileModule(source, { + fileName: file, + compilerOptions: { + target: ts.ScriptTarget.ES2022, + module: ts.ModuleKind.ESNext, + jsx: ts.JsxEmit.ReactJSX, + importsNotUsedAsValues: ts.ImportsNotUsedAsValues.Remove, + sourceMap: false, + }, + reportDiagnostics: true, + }); + const errors = (result.diagnostics ?? []).filter((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error); + if (errors.length) { + throw new Error(`${file}: ${errors.map((diagnostic) => ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')).join('\n')}`); + } + const outputName = file.replace(/\.tsx?$/, '.js'); + fs.writeFileSync(path.join(assetsDir, outputName), fixLocalImports(result.outputText)); +} + +fs.copyFileSync(path.join(srcDir, 'styles.css'), path.join(assetsDir, 'styles.css')); +for (const asset of ['glm-logo.png', 'glm-favicon.png', 'glm-tab-icon-16-20260722.png', 'glm-tab-icon-32-20260722.png', 'glm-tab-icon-64-20260722.png', 'glm-tab-icon-180-20260722.png', 'glm-tab-icon-20260722.ico']) { + const source = path.join(root, 'public', asset); + if (fs.existsSync(source)) fs.copyFileSync(source, path.join(distDir, asset)); +} + +const indexHtml = ` + + + + + + + + + + GLM Hub — GomezLee Marketing + + + + +
+ + + +`; +fs.writeFileSync(path.join(distDir, 'index.html'), indexHtml); +console.log(`Static ESM build created at ${distDir}`); diff --git a/scripts/build.ps1 b/scripts/build.ps1 new file mode 100644 index 0000000..6459497 --- /dev/null +++ b/scripts/build.ps1 @@ -0,0 +1,21 @@ +$ErrorActionPreference = "Stop" + +$projectRoot = [System.IO.Path]::GetFullPath((Split-Path -Parent $PSScriptRoot)) +Push-Location $projectRoot + +try { + & npm run typecheck + if ($LASTEXITCODE -ne 0) { + throw "TypeScript encontró errores." + } + + & npm exec vite build + if ($LASTEXITCODE -ne 0) { + throw "Vite no pudo compilar la aplicación." + } + + Write-Host "GLM Hub compilado en $(Join-Path $projectRoot 'dist')" +} +finally { + Pop-Location +} diff --git a/scripts/serve.mjs b/scripts/serve.mjs new file mode 100644 index 0000000..be4ca91 --- /dev/null +++ b/scripts/serve.mjs @@ -0,0 +1,40 @@ +import { createReadStream, existsSync, statSync } from "node:fs"; +import { createServer } from "node:http"; +import { extname, join, normalize, resolve } from "node:path"; + +const root = resolve(process.cwd(), process.argv[2] ?? "dist"); +const port = Number(process.env.PORT ?? 4173); +const mimeTypes = new Map([ + [".css", "text/css; charset=utf-8"], + [".html", "text/html; charset=utf-8"], + [".js", "text/javascript; charset=utf-8"], + [".json", "application/json; charset=utf-8"], + [".png", "image/png"], + [".svg", "image/svg+xml"], + [".webp", "image/webp"], +]); + +const server = createServer((request, response) => { + const rawPath = decodeURIComponent((request.url ?? "/").split("?")[0]); + const relativePath = normalize(rawPath).replace(/^([/\\])+/, ""); + let filePath = resolve(join(root, relativePath || "index.html")); + + if (!filePath.startsWith(root)) { + response.writeHead(403).end("Forbidden"); + return; + } + + if (!existsSync(filePath) || statSync(filePath).isDirectory()) { + filePath = join(root, "index.html"); + } + + response.setHeader("Content-Type", mimeTypes.get(extname(filePath)) ?? "application/octet-stream"); + response.setHeader("X-Content-Type-Options", "nosniff"); + response.setHeader("Referrer-Policy", "strict-origin-when-cross-origin"); + response.setHeader("Cross-Origin-Opener-Policy", "same-origin"); + createReadStream(filePath).pipe(response); +}); + +server.listen(port, "127.0.0.1", () => { + console.log(`GLM Hub disponible en http://127.0.0.1:${port}`); +}); diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..8bc3f88 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,2235 @@ +import { + ArrowLeft, + ArrowUpRight, + ChevronLeft, + ChevronRight, + Home, + ImageIcon, + Link2, + LockKeyhole, + LogOut, + Plus, + Save, + Search, + SlidersHorizontal, + Sparkles, + Star, + Trash2, + Upload, + X, +} from "lucide-react"; +import { + type ChangeEvent, + type DragEvent, + type FormEvent, + useCallback, + useDeferredValue, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { CATEGORY_ORDER } from "./data"; +import { getSafeUrl, loadHubData, normalizeSearch } from "./storage"; +import { + deleteHubApp, + fetchHubApps, + fetchHubData, + migrateLocalAppsIfNeeded, + recordRecent, + saveHubApp, + setFavorite, + subscribeToHubApps, +} from "./repository"; +import { supabase } from "./supabase"; +import type { + AppCategory, + AppVisibility, + HubUser, + HubApp, + HubData, + Session, + ToastMessage, +} from "./types"; + +type AppView = "hub" | "admin"; + +interface AppDraft { + name: string; + description: string; + category: AppCategory; + url: string; + iconDataUrl: string; + visibility: AppVisibility; +} + +const COLLATOR = new Intl.Collator("es-DO", { sensitivity: "base" }); +const AUTHORIZED_DOMAIN = "@gomezleemarketing.com"; +const ICON_GENERATOR_WEBHOOK_URL = import.meta.env.VITE_ICON_GENERATOR_WEBHOOK_URL?.trim() ?? ""; +const ACCESS_REQUEST_WEBHOOK_URL = import.meta.env.VITE_ACCESS_REQUEST_WEBHOOK_URL?.trim() ?? ""; + +interface SupabaseAuthSession { + access_token: string; + expires_at?: number; + user: { + id: string; + email?: string; + user_metadata?: Record; + }; +} + +interface AuthorizedUserRow { + email: string; + full_name: string | null; + role: "admin" | "member"; +} + +interface AuthorizationResult { + session: Session | null; + error: string; +} + +function getAppUrl(hash = ""): string { + return `${import.meta.env.BASE_URL}${hash}`; +} + +function getOAuthRedirectUrl(): string { + return new URL(import.meta.env.BASE_URL, window.location.origin).toString(); +} + +function getAuthenticatedName(authSession: SupabaseAuthSession, access: AuthorizedUserRow): string { + const metadata = authSession.user.user_metadata ?? {}; + const metadataName = metadata.full_name ?? metadata.name; + const emailName = access.email.split("@")[0] || "Usuario GLM"; + return access.full_name?.trim() || (typeof metadataName === "string" ? metadataName.trim() : "") || emailName; +} + +function getAuthenticatedAvatarUrl(authSession: SupabaseAuthSession): string { + const metadata = authSession.user.user_metadata ?? {}; + const candidate = metadata.avatar_url ?? metadata.picture; + if (typeof candidate !== "string" || !candidate.trim()) return ""; + + try { + const url = new URL(candidate.trim()); + return url.protocol === "https:" ? url.toString() : ""; + } catch { + return ""; + } +} + +async function authorizeSupabaseSession(authSession: SupabaseAuthSession): Promise { + const email = authSession.user.email?.trim().toLocaleLowerCase("en-US") ?? ""; + + if (!email.endsWith(AUTHORIZED_DOMAIN)) { + return { + session: null, + error: "Debes continuar con una cuenta corporativa @gomezleemarketing.com.", + }; + } + + const { data, error } = await supabase + .from("glm_hub_authorized_users") + .select("email, full_name, role") + .eq("email", email) + .eq("is_active", true) + .maybeSingle(); + const access = data as AuthorizedUserRow | null; + + if (error) { + console.error("No se pudo validar el acceso en Supabase:", error); + return { + session: null, + error: "No pudimos validar tu acceso en Supabase. Intenta nuevamente.", + }; + } + + if (!access) { + return { + session: null, + error: "Tu correo no está autorizado para entrar al GLM Hub.", + }; + } + + return { + session: { + user: { + id: authSession.user.id, + name: getAuthenticatedName(authSession, access), + email: access.email, + role: access.role, + avatarUrl: getAuthenticatedAvatarUrl(authSession), + }, + expiresAt: (authSession.expires_at ?? Math.floor(Date.now() / 1000) + 3600) * 1000, + }, + error: "", + }; +} + +function getInitials(name: string): string { + const words = name.trim().split(/\s+/).filter(Boolean); + if (words.length === 0) return "GL"; + if (words.length === 1) return words[0].slice(0, 2).toLocaleUpperCase("es-DO"); + const surnameIndex = words.length >= 4 ? words.length - 2 : words.length - 1; + return `${words[0][0]}${words[surnameIndex][0]}`.toLocaleUpperCase("es-DO"); +} + +function getGreeting(): string { + const hour = new Date().getHours(); + if (hour < 12) return "Buenos días"; + if (hour < 19) return "Buenas tardes"; + return "Buenas noches"; +} + +function capitalize(value: string): string { + return value ? value[0].toLocaleUpperCase("es-DO") + value.slice(1) : value; +} + +function formatDateLabel(date: Date): string { + const parts = new Intl.DateTimeFormat("es-DO", { + weekday: "long", + day: "numeric", + month: "long", + year: "numeric", + }).formatToParts(date); + + const getPart = (type: Intl.DateTimeFormatPartTypes) => parts.find((part) => part.type === type)?.value ?? ""; + return `${capitalize(getPart("weekday"))} ${getPart("day")} de ${capitalize(getPart("month"))} de ${getPart("year")}`; +} + +function useCurrentDateLabel(): string { + const [label, setLabel] = useState(() => formatDateLabel(new Date())); + + useEffect(() => { + const refresh = () => setLabel(formatDateLabel(new Date())); + refresh(); + const intervalId = window.setInterval(refresh, 60_000); + return () => window.clearInterval(intervalId); + }, []); + + return label; +} + +function makeEmptyDraft(): AppDraft { + return { + name: "", + description: "", + category: "Administración", + url: "", + iconDataUrl: "", + visibility: "published", + }; +} + +function appToDraft(app: HubApp): AppDraft { + return { + name: app.name, + description: app.description, + category: app.category, + url: app.url, + iconDataUrl: app.iconDataUrl, + visibility: app.visibility, + }; +} + +async function processIcon(file: File, enforceFileSize = true): Promise { + const allowedTypes = new Set(["image/png", "image/jpeg", "image/webp"]); + if (!allowedTypes.has(file.type)) { + throw new Error("Usa un ícono en formato PNG, JPG o WebP."); + } + if (enforceFileSize && file.size > 1024 * 1024) { + throw new Error("El ícono debe pesar menos de 1 MB."); + } + + const bitmap = await createImageBitmap(file); + if (bitmap.width > 4096 || bitmap.height > 4096) { + bitmap.close(); + throw new Error("El ícono no puede superar 4096 × 4096 px."); + } + + const canvas = document.createElement("canvas"); + canvas.width = 192; + canvas.height = 192; + const context = canvas.getContext("2d"); + if (!context) { + bitmap.close(); + throw new Error("Este navegador no pudo procesar el ícono."); + } + + const scale = Math.min(192 / bitmap.width, 192 / bitmap.height); + const targetWidth = bitmap.width * scale; + const targetHeight = bitmap.height * scale; + const targetX = (192 - targetWidth) / 2; + const targetY = (192 - targetHeight) / 2; + context.clearRect(0, 0, 192, 192); + context.drawImage(bitmap, targetX, targetY, targetWidth, targetHeight); + bitmap.close(); + return canvas.toDataURL("image/webp", 0.9); +} + +async function processGeneratedIcon(dataUrl: string): Promise { + if (!dataUrl.startsWith("data:image/")) throw new Error("La IA no devolvió una imagen válida."); + const response = await fetch(dataUrl); + const blob = await response.blob(); + return processGeneratedIconBlob(blob); +} + +async function processGeneratedIconBlob(blob: Blob): Promise { + const mimeType = blob.type || "image/png"; + if (!mimeType.startsWith("image/")) throw new Error("El flujo de IA no devolvió un archivo de imagen."); + const extension = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] || "png"; + const file = new File([blob], `icono-generado.${extension}`, { type: mimeType }); + return processIcon(file, false); +} + +function BrandLogo({ compact = false }: { compact?: boolean }) { + return ( + GomezLee Marketing + ); +} + +function AppMark({ + app, + size = "regular", + showPlaceholder = false, +}: { + app: HubApp; + size?: "small" | "regular" | "large"; + showPlaceholder?: boolean; +}) { + const [imageFailed, setImageFailed] = useState(false); + const showImage = Boolean(app.iconDataUrl) && !imageFailed; + + return ( + + ); +} + +function UserAvatar({ user, label }: { user: HubUser; label?: string }) { + const [imageFailed, setImageFailed] = useState(false); + const showImage = Boolean(user.avatarUrl) && !imageFailed; + + return ( + + {showImage ? ( + setImageFailed(true)} + /> + ) : ( + getInitials(user.name) + )} + + ); +} + +interface ToastStackProps { + messages: ToastMessage[]; + onDismiss: (id: string) => void; +} + +function ToastStack({ messages, onDismiss }: ToastStackProps) { + return ( +
+ {messages.map((message) => ( +
+
+ ))} +
+ ); +} + +interface LoginViewProps { + onLogin: () => Promise; + error: string; + disabled?: boolean; +} + +function LoginView({ onLogin, error, disabled = false }: LoginViewProps) { + const [submitting, setSubmitting] = useState(false); + + const handleLogin = async () => { + setSubmitting(true); + await onLogin(); + setSubmitting(false); + }; + + return ( +
+
+
+ +
+ +
+

ACCESO INTERNO

+

+ Todo el trabajo + de GLM, a un clic. +

+

+ Tu punto de partida para abrir herramientas, encontrar recursos y mantener el día en movimiento. +

+
+ + +
+ +
+
+
+

GLM HUB

+

Entra a tu espacio.

+
+ +
+ {error ? ( + + ) : null} + + +
+
+
+
+ ); +} + +interface SidebarProps { + user: HubUser; + view: AppView; + onNavigate: (view: AppView) => void; + onLogout: () => void; +} + +function Sidebar({ user, view, onNavigate, onLogout }: SidebarProps) { + return ( + + ); +} + +interface MobileHeaderProps { + user: HubUser; +} + +function MobileHeader({ user }: MobileHeaderProps) { + return ( +
+ + +
+ ); +} + +interface MobileNavProps extends SidebarProps {} + +function MobileNav({ user, view, onNavigate, onLogout }: MobileNavProps) { + return ( + + ); +} + +interface QuickLaunchProps { + app: HubApp; + isFavorite: boolean; + onActivate: (app: HubApp) => void; + onFavorite: (appId: string) => void; +} + +function QuickLaunch({ app, isFavorite, onActivate, onFavorite }: QuickLaunchProps) { + const available = Boolean(getSafeUrl(app.url)); + return ( +
+
+ + +
+
+ {app.category} +

{app.name}

+

{app.description}

+
+ +
+ ); +} + +interface DirectoryRowProps { + app: HubApp; + index: number; + isFavorite: boolean; + onActivate: (app: HubApp) => void; + onFavorite: (appId: string) => void; +} + +function DirectoryRow({ app, index, isFavorite, onActivate, onFavorite }: DirectoryRowProps) { + const available = Boolean(getSafeUrl(app.url)); + return ( +
+ + +
+

{app.name}

+

{app.description}

+
+ {app.category} + + + + +
+ ); +} + +interface EmptyResultsProps { + search: string; + favoritesOnly: boolean; + onClear: () => void; +} + +function EmptyResults({ search, favoritesOnly, onClear }: EmptyResultsProps) { + return ( +
+ +
+

SIN RESULTADOS

+

{favoritesOnly ? "Aún no tienes favoritos aquí." : `No encontramos “${search || "esa aplicación"}”.`}

+

Prueba otro término, cambia la categoría o vuelve a ver todo el directorio.

+ +
+
+ ); +} + +type AccessRequestArea = "Todas" | "Administración" | "Recursos Humanos" | "CDC"; + +interface AccessRequestSectionProps { + apps: HubApp[]; + onSubmit: (app: HubApp) => Promise; +} + +function AccessRequestSection({ apps, onSubmit }: AccessRequestSectionProps) { + const dialogRef = useRef(null); + const [area, setArea] = useState("Todas"); + const [appId, setAppId] = useState(""); + const [submitting, setSubmitting] = useState(false); + + const publishedApps = useMemo( + () => [...apps] + .filter((app) => app.visibility === "published") + .sort((a, b) => COLLATOR.compare(a.name, b.name)), + [apps], + ); + + const filteredApps = useMemo( + () => area === "Todas" ? publishedApps : publishedApps.filter((app) => app.category === area), + [area, publishedApps], + ); + + useEffect(() => { + if (appId && !filteredApps.some((app) => app.id === appId)) setAppId(""); + }, [appId, filteredApps]); + + useEffect(() => { + if (!submitting) return undefined; + const warnBeforeLeave = (event: BeforeUnloadEvent) => { + event.preventDefault(); + event.returnValue = ""; + }; + window.addEventListener("beforeunload", warnBeforeLeave); + return () => window.removeEventListener("beforeunload", warnBeforeLeave); + }, [submitting]); + + const openDialog = () => { + setArea("Todas"); + setAppId(""); + dialogRef.current?.showModal(); + }; + + const closeDialog = () => { + if (submitting) return; + dialogRef.current?.close(); + }; + + const handleSubmit = async (event: FormEvent) => { + event.preventDefault(); + const selectedApp = publishedApps.find((app) => app.id === appId); + if (!selectedApp || submitting) return; + + setSubmitting(true); + try { + const sent = await onSubmit(selectedApp); + if (sent) { + dialogRef.current?.close(); + setArea("Todas"); + setAppId(""); + } + } finally { + setSubmitting(false); + } + }; + + return ( +
+ +
+

SOLICITUD DE ACCESO

+

¿No tienes acceso a una aplicación?

+

Selecciona el portal que necesitas y envía la solicitud al equipo responsable.

+
+ + + { + if (submitting) event.preventDefault(); + }} + > + +

GLM HUB

+

Solicitar acceso

+

+ Elige el área y la aplicación. El catálogo se actualiza automáticamente con los portales publicados. +

+ +
void handleSubmit(event)}> +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ + {submitting ? ( +
+
+
+
+ ) : null} +
+ ); +} + +type PaginationItem = number | "ellipsis-start" | "ellipsis-end"; + +function getPaginationItems(currentPage: number, totalPages: number, compact = false): PaginationItem[] { + const visibleLimit = compact ? 5 : 7; + if (totalPages <= visibleLimit) { + return Array.from({ length: totalPages }, (_, index) => index + 1); + } + + if (compact) { + if (currentPage <= 3) return [1, 2, 3, "ellipsis-end", totalPages]; + if (currentPage >= totalPages - 2) { + return [1, "ellipsis-start", totalPages - 2, totalPages - 1, totalPages]; + } + return [1, "ellipsis-start", currentPage, "ellipsis-end", totalPages]; + } + + if (currentPage <= 4) return [1, 2, 3, 4, 5, "ellipsis-end", totalPages]; + if (currentPage >= totalPages - 3) { + return [ + 1, + "ellipsis-start", + totalPages - 4, + totalPages - 3, + totalPages - 2, + totalPages - 1, + totalPages, + ]; + } + return [ + 1, + "ellipsis-start", + currentPage - 1, + currentPage, + currentPage + 1, + "ellipsis-end", + totalPages, + ]; +} + +interface PaginationProps { + currentPage: number; + totalPages: number; + onPageChange: (page: number) => void; + compact?: boolean; + label: string; +} + +function Pagination({ currentPage, totalPages, onPageChange, compact = false, label }: PaginationProps) { + if (totalPages <= 1) return null; + const items = getPaginationItems(currentPage, totalPages, compact); + + return ( + + ); +} + +const HUB_ITEMS_PER_PAGE = 10; +const QUICK_ITEMS_PER_PAGE = 6; +const ADMIN_ITEMS_PER_PAGE = 10; + +interface HubViewProps { + user: HubUser; + data: HubData; + storageWarning: string; + onActivate: (app: HubApp) => void; + onFavorite: (appId: string) => void; + onAdmin: () => void; + onRequestAccess: (app: HubApp) => Promise; +} + +function HubView({ user, data, storageWarning, onActivate, onFavorite, onAdmin, onRequestAccess }: HubViewProps) { + const [search, setSearch] = useState(""); + const [category, setCategory] = useState<"Todas" | AppCategory>("Todas"); + const [favoritesOnly, setFavoritesOnly] = useState(false); + const [currentPage, setCurrentPage] = useState(1); + const [quickPage, setQuickPage] = useState(1); + const deferredSearch = useDeferredValue(search); + const dateLabel = useCurrentDateLabel(); + const canManageCatalog = user.role === "admin"; + const favoriteIds = data.favoritesByUser[user.id] ?? []; + const recentIds = data.recentByUser[user.id] ?? []; + const favorites = useMemo(() => new Set(favoriteIds), [favoriteIds]); + + const publishedApps = useMemo( + () => [...data.apps.filter((app) => app.visibility === "published" && Boolean(app.iconDataUrl.trim()))] + .sort((a, b) => COLLATOR.compare(a.name, b.name)), + [data.apps], + ); + + const results = useMemo(() => { + const query = normalizeSearch(deferredSearch); + return publishedApps + .filter((app) => { + if (category !== "Todas" && app.category !== category) return false; + if (favoritesOnly && !favorites.has(app.id)) return false; + if (!query) return true; + return normalizeSearch(app.name).includes(query); + }) + .sort((a, b) => { + const favoriteDifference = Number(favorites.has(b.id)) - Number(favorites.has(a.id)); + return favoriteDifference || COLLATOR.compare(a.name, b.name); + }); + }, [category, deferredSearch, favorites, favoritesOnly, publishedApps]); + + const totalPages = Math.max(1, Math.ceil(results.length / HUB_ITEMS_PER_PAGE)); + + useEffect(() => { + setCurrentPage(1); + }, [search, category, favoritesOnly]); + + useEffect(() => { + if (currentPage > totalPages) setCurrentPage(totalPages); + }, [currentPage, totalPages]); + + const paginatedResults = useMemo(() => { + const startIndex = (currentPage - 1) * HUB_ITEMS_PER_PAGE; + return results.slice(startIndex, startIndex + HUB_ITEMS_PER_PAGE); + }, [currentPage, results]); + + const changeDirectoryPage = (page: number) => { + setCurrentPage(Math.min(Math.max(page, 1), totalPages)); + window.requestAnimationFrame(() => { + document.getElementById("directory-title")?.scrollIntoView({ behavior: "smooth", block: "start" }); + }); + }; + + const quickApps = useMemo(() => { + const byId = new Map(publishedApps.map((app) => [app.id, app])); + const chosen: HubApp[] = []; + const chosenIds = new Set(); + const add = (id: string) => { + const app = byId.get(id); + if (!app || chosenIds.has(id)) return; + chosen.push(app); + chosenIds.add(id); + }; + + favoriteIds.forEach(add); + if (chosen.length < 3) { + for (const id of recentIds) { + add(id); + if (chosen.length >= 3) break; + } + } + if (chosen.length < 3) { + for (const app of publishedApps) { + add(app.id); + if (chosen.length >= 3) break; + } + } + return chosen; + }, [favoriteIds, publishedApps, recentIds]); + + const quickTotalPages = Math.max(1, Math.ceil(quickApps.length / QUICK_ITEMS_PER_PAGE)); + const paginatedQuickApps = useMemo(() => { + const startIndex = (quickPage - 1) * QUICK_ITEMS_PER_PAGE; + return quickApps.slice(startIndex, startIndex + QUICK_ITEMS_PER_PAGE); + }, [quickApps, quickPage]); + + useEffect(() => { + if (quickPage > quickTotalPages) setQuickPage(quickTotalPages); + }, [quickPage, quickTotalPages]); + + useEffect(() => { + setQuickPage(1); + }, [data.favoritesByUser, data.recentByUser, user.id]); + + const changeQuickPage = (page: number) => { + setQuickPage(Math.min(Math.max(page, 1), quickTotalPages)); + window.requestAnimationFrame(() => { + document.getElementById("quick-title")?.scrollIntoView({ behavior: "smooth", block: "start" }); + }); + }; + + const clearFilters = () => { + setSearch(""); + setCategory("Todas"); + setFavoritesOnly(false); + }; + + return ( +
+
+
+

{getGreeting()}, {user.name}

+

Tu mesa de trabajo.

+
+
+ {dateLabel} +
+
+ + {storageWarning ?
{storageWarning}
: null} + +
+
+

¿Qué necesitas abrir?

+
+
+
+
+ +
+
+
+

FIJADAS PARA TI

+

Acceso rápido

+
+

Abre lo que más usas sin detener el ritmo.

+
+
+ {paginatedQuickApps.map((app) => ( + + ))} +
+ 5} + label="Paginación de acceso rápido" + /> +
+ +
+
+
+

DIRECTORIO GLM

+

Todas las aplicaciones

+
+
+ {results.length} + {results.length === 1 ? "resultado" : "resultados"} +
+
+ +
+
+ {(["Todas", ...CATEGORY_ORDER] as const).map((item) => ( + + ))} +
+ +
+ + {results.length > 0 ? ( + <> +
+ {paginatedResults.map((app, index) => ( + + ))} +
+ 5} + label="Paginación de aplicaciones" + /> + + ) : ( + + )} +
+ + {canManageCatalog ? ( +
+ +
+

CONTROL DEL CATÁLOGO

+

¿Falta una herramienta?

+

Adjunta su ícono, pega el enlace y publícala para todo el equipo.

+
+ +
+ ) : ( + + )} +
+ ); +} + +interface AdminAppListProps { + apps: HubApp[]; + selectedId: string | null; + onSelect: (id: string | null) => void; + onDelete: (id: string) => Promise; +} + + +function AdminAppList({ apps, selectedId, onSelect, onDelete }: AdminAppListProps) { + const [query, setQuery] = useState(""); + const [currentPage, setCurrentPage] = useState(1); + const [appToDelete, setAppToDelete] = useState(null); + const deleteModalRef = useRef(null); + + const filtered = useMemo(() => { + const normalized = normalizeSearch(query); + if (!normalized) return apps; + return apps.filter((app) => normalizeSearch(`${app.name} ${app.category}`).includes(normalized)); + }, [apps, query]); + + const totalPages = Math.max(1, Math.ceil(filtered.length / ADMIN_ITEMS_PER_PAGE)); + + useEffect(() => { + setCurrentPage(1); + }, [query]); + + useEffect(() => { + if (currentPage > totalPages) { + setCurrentPage(totalPages); + } + }, [currentPage, totalPages]); + + const paginatedApps = useMemo(() => { + const start = (currentPage - 1) * ADMIN_ITEMS_PER_PAGE; + return filtered.slice(start, start + ADMIN_ITEMS_PER_PAGE); + }, [filtered, currentPage]); + + const handleOpenDelete = (e: React.MouseEvent, app: HubApp) => { + e.stopPropagation(); + setAppToDelete(app); + deleteModalRef.current?.showModal(); + }; + + const handleConfirmDelete = async () => { + if (!appToDelete) return; + if (await onDelete(appToDelete.id)) { + deleteModalRef.current?.close(); + setAppToDelete(null); + } + }; + + return ( + + ); +} + +interface AppEditorProps { + app: HubApp | null; + allApps: HubApp[]; + onSave: (draft: AppDraft, id: string | null) => Promise; + onDelete: (id: string) => Promise; + onCancel: () => void; +} + +function RequiredMark() { + return ( + <> + + obligatorio + + ); +} + +function AppEditor({ app, allApps, onSave, onDelete, onCancel }: AppEditorProps) { + const [draft, setDraft] = useState(() => (app ? appToDraft(app) : makeEmptyDraft())); + const [baseline, setBaseline] = useState(() => JSON.stringify(app ? appToDraft(app) : makeEmptyDraft())); + const [errors, setErrors] = useState>({}); + const [iconError, setIconError] = useState(""); + const [processingIcon, setProcessingIcon] = useState(false); + const [generatingIcon, setGeneratingIcon] = useState(false); + const [saving, setSaving] = useState(false); + const [dragActive, setDragActive] = useState(false); + const deleteDialogRef = useRef(null); + const firstFieldRef = useRef(null); + const isDirty = JSON.stringify(draft) !== baseline; + const isBusy = processingIcon || generatingIcon || saving; + const canGenerateIcon = draft.name.trim().length >= 2 && Boolean(draft.description.trim()); + + useEffect(() => { + const next = app ? appToDraft(app) : makeEmptyDraft(); + setDraft(next); + setBaseline(JSON.stringify(next)); + setErrors({}); + setIconError(""); + window.requestAnimationFrame(() => firstFieldRef.current?.focus()); + }, [app]); + + useEffect(() => { + if (!isDirty && !generatingIcon) return undefined; + const warn = (event: BeforeUnloadEvent) => event.preventDefault(); + window.addEventListener("beforeunload", warn); + return () => window.removeEventListener("beforeunload", warn); + }, [generatingIcon, isDirty]); + + useEffect(() => { + if (!generatingIcon) return undefined; + const previousOverflow = document.body.style.overflow; + document.body.style.overflow = "hidden"; + return () => { + document.body.style.overflow = previousOverflow; + }; + }, [generatingIcon]); + + const previewApp: HubApp = { + id: app?.id ?? "preview", + name: draft.name || "Nombre de la aplicación", + description: draft.description || "Una descripción breve ayuda al equipo a reconocerla.", + category: draft.category, + keywords: [], + url: draft.url, + mark: getInitials(draft.name || "GLM"), + accent: app?.accent ?? "#4F758B", + iconDataUrl: draft.iconDataUrl, + visibility: draft.visibility, + createdAt: app?.createdAt ?? new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + + const setField = (field: K, value: AppDraft[K]) => { + setDraft((current) => ({ ...current, [field]: value })); + setErrors((current) => ({ ...current, [field]: "" })); + }; + + const acceptIcon = async (file: File | undefined) => { + if (!file || generatingIcon) return; + setIconError(""); + setProcessingIcon(true); + try { + const dataUrl = await processIcon(file); + setField("iconDataUrl", dataUrl); + } catch (error) { + setIconError(error instanceof Error ? error.message : "No pudimos procesar el ícono."); + } finally { + setProcessingIcon(false); + } + }; + + const handleFileChange = (event: ChangeEvent) => { + void acceptIcon(event.target.files?.[0]); + event.target.value = ""; + }; + + const handleDrop = (event: DragEvent) => { + event.preventDefault(); + setDragActive(false); + void acceptIcon(event.dataTransfer.files?.[0]); + }; + + const handleGenerateIcon = async () => { + const name = draft.name.trim(); + const description = draft.description.trim(); + const nextErrors: Record = {}; + if (name.length < 2) nextErrors.name = "Completa el nombre antes de generar el ícono."; + if (!description) nextErrors.description = "Completa la descripción antes de generar el ícono."; + if (Object.keys(nextErrors).length > 0) { + setErrors((current) => ({ ...current, ...nextErrors })); + return; + } + if (!ICON_GENERATOR_WEBHOOK_URL) { + setIconError("Falta configurar VITE_ICON_GENERATOR_WEBHOOK_URL en el archivo .env."); + return; + } + + setIconError(""); + setGeneratingIcon(true); + const controller = new AbortController(); + const timeoutId = window.setTimeout(() => controller.abort(), 180_000); + try { + const { data: { session: authSession } } = await supabase.auth.getSession(); + if (!authSession?.access_token) throw new Error("La sesión venció. Inicia sesión nuevamente."); + + // El JWT viaja dentro de un cuerpo text/plain para evitar el preflight CORS + // que impedía que la solicitud llegara al webhook de n8n desde el navegador. + const response = await fetch(ICON_GENERATOR_WEBHOOK_URL, { + method: "POST", + mode: "cors", + cache: "no-store", + credentials: "omit", + signal: controller.signal, + headers: { + Accept: "image/*, application/json;q=0.9, text/plain;q=0.8", + "Content-Type": "text/plain;charset=UTF-8", + }, + body: JSON.stringify({ + accessToken: authSession.access_token, + name, + description, + }), + }); + + const contentType = response.headers.get("content-type")?.toLocaleLowerCase("en-US") ?? ""; + if (response.ok && contentType.startsWith("image/")) { + const normalizedIcon = await processGeneratedIconBlob(await response.blob()); + setField("iconDataUrl", normalizedIcon); + return; + } + + const responseText = await response.text(); + let payload: { ok?: boolean; imageDataUrl?: string; error?: string } | null = null; + if (responseText) { + try { + payload = JSON.parse(responseText) as { ok?: boolean; imageDataUrl?: string; error?: string }; + } catch { + payload = null; + } + } + + // Compatibilidad con una respuesta JSON de una versión anterior del workflow. + if (response.ok && payload?.ok && payload.imageDataUrl) { + const normalizedIcon = await processGeneratedIcon(payload.imageDataUrl); + setField("iconDataUrl", normalizedIcon); + return; + } + + throw new Error( + payload?.error || + (responseText && responseText.length < 500 ? responseText : "") || + `El flujo no pudo generar el ícono (HTTP ${response.status}).`, + ); + } catch (error) { + if (error instanceof DOMException && error.name === "AbortError") { + setIconError("La generación tardó más de 3 minutos y fue cancelada. Intenta nuevamente."); + } else if (error instanceof TypeError && /failed to fetch/i.test(error.message)) { + setIconError("No se pudo conectar con el webhook de n8n. Verifica que el workflow esté activo y publicado."); + } else { + setIconError(error instanceof Error ? error.message : "No pudimos generar el ícono con IA."); + } + } finally { + window.clearTimeout(timeoutId); + setGeneratingIcon(false); + } + }; + + const validate = (): boolean => { + const nextErrors: Record = {}; + const name = draft.name.trim(); + const description = draft.description.trim(); + if (name.length < 2 || name.length > 60) nextErrors.name = "Escribe un nombre de 2 a 60 caracteres."; + if (!description) nextErrors.description = "Añade una descripción breve."; + if (description.length > 180) nextErrors.description = "La descripción no puede superar 180 caracteres."; + if (!draft.iconDataUrl) nextErrors.iconDataUrl = "Adjunta o genera un ícono antes de publicar la aplicación."; + if (draft.url.trim() && !getSafeUrl(draft.url)) { + nextErrors.url = "Usa una URL https:// válida, sin credenciales embebidas."; + } + const normalizedName = normalizeSearch(name); + const duplicate = allApps.some((candidate) => candidate.id !== app?.id && normalizeSearch(candidate.name) === normalizedName); + if (duplicate) nextErrors.name = "Ya existe una aplicación con ese nombre."; + setErrors(nextErrors); + return Object.keys(nextErrors).length === 0; + }; + + const handleSubmit = async (event: FormEvent) => { + event.preventDefault(); + if (isBusy || !validate()) { + window.requestAnimationFrame(() => document.querySelector(".field-error")?.focus?.()); + return; + } + + setSaving(true); + try { + const saved = await onSave( + { + ...draft, + name: draft.name.trim(), + description: draft.description.trim(), + url: draft.url.trim(), + }, + app?.id ?? null, + ); + if (saved) { + const next = appToDraft(saved); + setDraft(next); + setBaseline(JSON.stringify(next)); + } + } finally { + setSaving(false); + } + }; + + const confirmCancel = () => { + if (generatingIcon) return; + if (!isDirty || window.confirm("Hay cambios sin guardar. ¿Quieres descartarlos?")) { + const next = app ? appToDraft(app) : makeEmptyDraft(); + setDraft(next); + setBaseline(JSON.stringify(next)); + setErrors({}); + setIconError(""); + onCancel(); + } + }; + + const confirmDelete = async () => { + if (!app || isBusy) return; + if (await onDelete(app.id)) { + deleteDialogRef.current?.close(); + onCancel(); + } + }; + + return ( +
+
+
+

{app ? "EDITAR APLICACIÓN" : "NUEVA APLICACIÓN"}

+

{app ? app.name : "Configura el nuevo acceso"}

+
+ {isDirty ? ( + + + ) : null} +
+ +
+
+
+

Identidad

+
+ + setField("name", event.target.value)} + placeholder="Ej. Media Planner" + maxLength={60} + aria-invalid={Boolean(errors.name)} + aria-describedby={errors.name ? "app-name-error" : undefined} + disabled={isBusy} + required + /> +
+ {errors.name ? {errors.name} : Como el equipo la reconoce.} + {draft.name.length}/60 +
+
+ +
+ +