From 0567c666d3ac2682118bd88f9623cbb388e2602b Mon Sep 17 00:00:00 2001 From: Isaac Aracena Date: Thu, 13 Aug 2026 18:21:34 -0400 Subject: [PATCH] feat: actualizar Tablero CDC con administracion de listas --- .env.example | 1 + CORRECCION_BUILD_Y_MODAL_TARIFARIO.md | 8 + LISTAS_APP_IMPLEMENTACION.md | 30 + MODULO_TARIFARIO_ADMIN_IMPLEMENTACION.md | 76 + NOTIFICACIONES_BRIEFS_NUEVOS.md | 44 + PAGINACION_MODULO_TARIFARIO.md | 20 + README.md | 724 +++------- REAPROBAR_BRIEFS_RECHAZADOS.md | 25 + TIEMPO_PROYECTO_IMPLEMENTACION.md | 38 + VALIDACION_MODULO_TARIFARIO.md | 42 + WMC_TARIFARIO_IMPLEMENTACION.md | 25 + dist/assets/index-COXhoke_.js | 433 ------ dist/assets/index-D0h6tUNa.css | 1 - dist/assets/index-DCvZdXJT.js | 479 ------ dist/assets/index-YGpmp8kO.css | 1 - dist/index.html | 4 +- index.html | 8 +- public/apple-touch-icon.png | Bin 6082 -> 7347 bytes public/favicon-16.png | Bin 0 -> 519 bytes public/favicon-32.png | Bin 0 -> 1248 bytes public/favicon-48.png | Bin 0 -> 1962 bytes public/favicon.ico | Bin 436 -> 23377 bytes public/favicon.png | Bin 8256 -> 24848 bytes public/favicon.svg | 5 +- public/glm-favicon.png | Bin 8755 -> 24848 bytes public/glm-logo-full.png | Bin 6233 -> 6004 bytes public/glm-logo.png | Bin 6233 -> 6004 bytes public/glm-tab-icon-v12.png | Bin 0 -> 24848 bytes public/glm-tab-icon.png | Bin 0 -> 24848 bytes public/glm-tab-icon.svg | 3 + public/glm-wordmark-favicon-v4.png | Bin 0 -> 24848 bytes public/icon-192.png | Bin 6314 -> 7802 bytes public/icon-512.png | Bin 8755 -> 24848 bytes src/App.tsx | 2 +- src/components/LoginScreen.tsx | 4 +- src/components/board/BriefReviewCard.tsx | 207 +++ src/components/board/ProjectCard.tsx | 59 +- src/components/board/ProjectDialog.tsx | 78 +- src/components/board/ProjectPricingPanel.tsx | 734 +++++++--- src/components/board/ProjectTimeDialog.tsx | 710 +++++++++ src/components/board/SearchableSelect.tsx | 37 +- src/components/lists/ListManagerDialog.tsx | 429 ++++++ src/components/tariff/TariffManagerDialog.tsx | 1283 +++++++++++++++++ src/context/AuthContext.tsx | 6 + src/data/lists.ts | 1 + src/data/tariff.ts | 168 ++- src/lib/accessControl.ts | 36 +- src/lib/appLists.ts | 20 +- src/lib/briefInbox.ts | 491 +++++++ src/lib/filterNormalization.ts | 246 ++++ src/lib/listAdmin.ts | 84 ++ src/lib/optionUtils.ts | 10 +- src/lib/store.ts | 333 ++++- src/lib/tariffCatalog.ts | 92 +- src/lib/tariffSections.ts | 264 ++++ src/pages/BoardPage.tsx | 354 ++++- supabase_cdc_brief_reapprove_rejected.sql | 191 +++ supabase_cdc_brief_review_inbox.sql | 572 ++++++++ supabase_cdc_brief_unread_notifications.sql | 135 ++ supabase_pricing_summary_visibility.sql | 4 +- supabase_project_time_entries.sql | 188 +++ supabase_tablero_cdc_access_control.sql | 4 +- supabase_tablero_cdc_alicia_permissions.sql | 23 + supabase_tariff_admin_module.sql | 829 +++++++++++ supabase_walmart_connect_tariff.sql | 422 ++++++ 65 files changed, 8120 insertions(+), 1863 deletions(-) create mode 100644 CORRECCION_BUILD_Y_MODAL_TARIFARIO.md create mode 100644 LISTAS_APP_IMPLEMENTACION.md create mode 100644 MODULO_TARIFARIO_ADMIN_IMPLEMENTACION.md create mode 100644 NOTIFICACIONES_BRIEFS_NUEVOS.md create mode 100644 PAGINACION_MODULO_TARIFARIO.md create mode 100644 REAPROBAR_BRIEFS_RECHAZADOS.md create mode 100644 TIEMPO_PROYECTO_IMPLEMENTACION.md create mode 100644 VALIDACION_MODULO_TARIFARIO.md create mode 100644 WMC_TARIFARIO_IMPLEMENTACION.md delete mode 100644 dist/assets/index-COXhoke_.js delete mode 100644 dist/assets/index-D0h6tUNa.css delete mode 100644 dist/assets/index-DCvZdXJT.js delete mode 100644 dist/assets/index-YGpmp8kO.css create mode 100644 public/favicon-16.png create mode 100644 public/favicon-32.png create mode 100644 public/favicon-48.png create mode 100644 public/glm-tab-icon-v12.png create mode 100644 public/glm-tab-icon.png create mode 100644 public/glm-tab-icon.svg create mode 100644 public/glm-wordmark-favicon-v4.png create mode 100644 src/components/board/BriefReviewCard.tsx create mode 100644 src/components/board/ProjectTimeDialog.tsx create mode 100644 src/components/lists/ListManagerDialog.tsx create mode 100644 src/components/tariff/TariffManagerDialog.tsx create mode 100644 src/lib/briefInbox.ts create mode 100644 src/lib/filterNormalization.ts create mode 100644 src/lib/listAdmin.ts create mode 100644 src/lib/tariffSections.ts create mode 100644 supabase_cdc_brief_reapprove_rejected.sql create mode 100644 supabase_cdc_brief_review_inbox.sql create mode 100644 supabase_cdc_brief_unread_notifications.sql create mode 100644 supabase_project_time_entries.sql create mode 100644 supabase_tablero_cdc_alicia_permissions.sql create mode 100644 supabase_tariff_admin_module.sql create mode 100644 supabase_walmart_connect_tariff.sql diff --git a/.env.example b/.env.example index bbfc411..5ec386d 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,4 @@ VITE_SUPABASE_URL="https://dbit.digitalcompass.agency" VITE_SUPABASE_ANON_KEY="TU_ANON_PUBLIC_KEY" VITE_WEBHOOK_URL="https://agenteit.digitalcompass.agency/webhook/tablero-cdc-sync-proyecto" +VITE_LISTS_WEBHOOK_URL="https://agenteit.digitalcompass.agency/webhook/tablero-cdc-sync-listas-admin" diff --git a/CORRECCION_BUILD_Y_MODAL_TARIFARIO.md b/CORRECCION_BUILD_Y_MODAL_TARIFARIO.md new file mode 100644 index 0000000..f6de06d --- /dev/null +++ b/CORRECCION_BUILD_Y_MODAL_TARIFARIO.md @@ -0,0 +1,8 @@ +# Corrección aplicada + +- **Build:** corregido el `TS2322` de `src/lib/tariffCatalog.ts` sin cambiar la lógica funcional. +- **Producción/XAMPP:** el modal del tarifario ya no se monta dentro del encabezado; se abre en `document.body` mediante portal para impedir el cuadro blanco o recortado. +- **Caché:** el `dist/index.html` utiliza nombres nuevos de assets y un identificador de versión. +- **Compatibilidad:** se mantienen copias con los nombres anteriores de los assets. + +Para probar el paquete, elimina primero el contenido anterior de `C:\xampp\htdocs\tablero-cdc` y copia allí el contenido de la carpeta `dist` incluida. diff --git a/LISTAS_APP_IMPLEMENTACION.md b/LISTAS_APP_IMPLEMENTACION.md new file mode 100644 index 0000000..a34bccb --- /dev/null +++ b/LISTAS_APP_IMPLEMENTACION.md @@ -0,0 +1,30 @@ +# Administración instantánea de listas — Tablero CDC + +## Objetivo +Permitir que los administradores actualicen desde Tablero CDC las listas utilizadas por el formulario sin dejar de usar Google Sheet como fuente oficial. + +## Fuente oficial +- Spreadsheet: `CD_Latam_Registro Horas_2024` +- Hoja: `Listas` +- La app nunca escribe directamente en `tablero_cdc_app_lists`. +- La app llama n8n, n8n modifica la hoja `Listas` y luego sincroniza Supabase. +- El workflow programado se mantiene como reconciliación de respaldo. + +## Listas administrables +- Cliente → columna A (`Cliente`) +- Marca → columna R (`Marca`). En la exportación recibida esa columna está vacía y no tiene referencias; el workflow la inicializa de forma segura conservando las marcas activas de Supabase y el catálogo de respaldo antes de sincronizar. +- País / BU + Country Manager → columnas C:D (`Pais / BU`, `CM`) +- Estatus → columna N (`estatus`) + +## Seguridad +- El botón `Listas` usa el mismo permiso existente `can_manage_tariff_catalog`. +- El webhook valida la sesión de Supabase y vuelve a comprobar ese permiso antes de permitir cualquier escritura. +- Los cambios no eliminan proyectos históricos; solo modifican opciones disponibles para nuevas ediciones/selecciones. + +## Actualización inmediata +1. El administrador crea, edita o elimina una opción. +2. n8n actualiza únicamente la columna correspondiente del Google Sheet. +3. n8n ejecuta el mismo RPC existente `tablero_cdc_sync_app_lists`. +4. Supabase Realtime notifica a las sesiones abiertas y `useAppLists()` recarga las listas. + +También existe `Sincronizar desde Sheet` para aplicar manualmente cambios hechos directamente en Google Sheet sin esperar el Schedule Trigger. El flujo programado conserva su intervalo existente de 3 horas. diff --git a/MODULO_TARIFARIO_ADMIN_IMPLEMENTACION.md b/MODULO_TARIFARIO_ADMIN_IMPLEMENTACION.md new file mode 100644 index 0000000..a7f55ea --- /dev/null +++ b/MODULO_TARIFARIO_ADMIN_IMPLEMENTACION.md @@ -0,0 +1,76 @@ +# Módulo de administración del tarifario — Tablero CDC + +## Qué incorpora + +- Botón **Tarifario** inmediatamente antes de **+ Nuevo**. +- El botón depende exclusivamente de `tablero_cdc_allowed_users.can_manage_tariff_catalog`. +- Administración de las secciones generales existentes: + - Tarifario Gráfico CDC. + - Estrategia y Creatividad. +- Creación de nuevas secciones generales. +- Creación de tarifarios especiales vinculados a cualquier cliente activo del Tablero CDC. +- Edición, desactivación y reactivación de secciones y tarifas. +- Las tarifas especiales se muestran únicamente cuando el proyecto tiene seleccionado el cliente asociado. +- Cada tarifario especial utiliza selección múltiple de piezas con montos fijos y puede permitir un costo manual adicional. +- Los proyectos históricos no se modifican. + +## Orden de instalación + +1. Ejecutar completo `supabase_tariff_admin_module.sql` en Supabase. +2. Confirmar que las consultas de verificación muestran a Isaac, Gerardo y Alicia con `can_manage_tariff_catalog = true`. +3. Ejecutar `npm run build` o utilizar el `dist` incluido, que ya contiene el módulo. +4. Probar localmente con XAMPP. +5. Publicar el `dist` validado. + +## Dar acceso a otra persona + +La persona debe existir primero como usuario activo del Tablero CDC. Luego ejecutar: + +```sql +update public.tablero_cdc_allowed_users +set can_manage_tariff_catalog = true, + updated_at = now() +where lower(trim(email)) = lower(trim('correo@gomezleemarketing.com')); +``` + +## Quitar acceso al módulo + +```sql +update public.tablero_cdc_allowed_users +set can_manage_tariff_catalog = false, + updated_at = now() +where lower(trim(email)) = lower(trim('correo@gomezleemarketing.com')); +``` + +Después del cambio, el usuario debe recargar la aplicación. Si tenía una sesión abierta desde antes, puede cerrar sesión y volver a entrar para que el permiso se lea nuevamente. + +## Verificar quién tiene acceso + +```sql +select + email, + full_name, + is_active, + can_manage_tariff_catalog +from public.tablero_cdc_allowed_users +where can_manage_tariff_catalog = true +order by full_name, email; +``` + +## Pruebas recomendadas + +1. Isaac, Gerardo y Alicia ven el botón **Tarifario**. +2. Un usuario sin el permiso no ve el botón y tampoco puede escribir en las tablas por RLS. +3. Editar una tarifa de una sección general y verificarla dentro de un proyecto de un cliente normal. +4. Crear una sección general nueva, agregar una tarifa y verificar que aparezca en el estimador interno. +5. Crear un tarifario por cliente, asociarlo a un cliente de prueba y agregar dos piezas con montos fijos. +6. Abrir un proyecto de ese cliente y verificar selección múltiple, subtotal y costo manual. +7. Abrir un proyecto de otro cliente y confirmar que el tarifario especial no aparezca. +8. Desactivar una tarifa y comprobar que deja de ofrecerse sin borrar costos históricos. + +## Compatibilidad con el sincronizador del Sheet + +- Las tarifas administradas desde la app se marcan con `managed_by = 'app'`. +- El sincronizador de n8n no puede sobrescribir tarifas que ya hayan sido administradas desde la app. +- La desactivación masiva del Sheet solo afecta las tarifas gestionadas por el Sheet en las secciones originales. +- Las nuevas secciones y los tarifarios especiales permanecen protegidos. diff --git a/NOTIFICACIONES_BRIEFS_NUEVOS.md b/NOTIFICACIONES_BRIEFS_NUEVOS.md new file mode 100644 index 0000000..768d716 --- /dev/null +++ b/NOTIFICACIONES_BRIEFS_NUEVOS.md @@ -0,0 +1,44 @@ +# Notificaciones personales — CDC Brief / Nuevos + +Actualización: 2026-08-12 + +## Comportamiento + +La navegación queda en este orden: + +`Todos → Activos → Cerrados → Nuevos → No aprobados` + +El apartado **Nuevos** muestra un badge rojo únicamente cuando el usuario autenticado tiene briefs pendientes que todavía no ha visto. + +- Cada usuario mantiene su propio estado de lectura en Supabase. +- Entrar a **Nuevos** marca como vistos, para ese usuario, todos los briefs que estén pendientes en ese momento y el badge desaparece. +- El hecho de que un usuario entre a **Nuevos** no borra la notificación de los demás usuarios. +- Si otra persona aprueba o no aprueba un brief, deja de contar como pendiente para todos y los contadores se actualizan. +- Un brief que llegue después vuelve a incrementar el badge de quienes todavía no lo hayan visto. +- Mientras el usuario permanece en **Nuevos**, los briefs nuevos que aparecen en la cola visible se marcan como vistos para esa persona. + +## Supabase + +El estado por usuario se guarda en: + +`public.tablero_cdc_brief_inbox_seen` + +RPCs nuevas: + +- `tablero_cdc_get_unseen_brief_count()` +- `tablero_cdc_mark_briefs_seen()` + +La solución usa una relación `(user_id, inbox_id)` y no un timestamp global, evitando que un brief que llegue concurrentemente quede marcado como visto por accidente. + +## SQL a ejecutar + +- Si todavía **no** se había ejecutado el SQL anterior de la bandeja: ejecutar `supabase_cdc_brief_review_inbox.sql` completo. +- Si el SQL anterior **ya** estaba instalado: ejecutar solamente `supabase_cdc_brief_unread_notifications.sql`. + +## Producción / dist + +Se conserva el bundle principal ya probado. El nuevo comportamiento de la bandeja se carga desde: + +`dist/assets/brief-review-inbox-v2.js` + +En el código fuente React la actualización usa Supabase Realtime y un fallback periódico. El módulo de `dist` conserva un refresco periódico y por foco/visibilidad para mantener compatibilidad con el build híbrido existente sin reemplazar el bundle principal. diff --git a/PAGINACION_MODULO_TARIFARIO.md b/PAGINACION_MODULO_TARIFARIO.md new file mode 100644 index 0000000..63402ef --- /dev/null +++ b/PAGINACION_MODULO_TARIFARIO.md @@ -0,0 +1,20 @@ +# Paginación del módulo de tarifario + +Fecha: 2026-07-28 + +## Implementación + +- Lista lateral de secciones: **6 secciones por página**. +- Lista principal de tarifas/piezas: **10 registros por página**. +- Los controles muestran el rango visible, el total y la página actual. +- Al cambiar entre **Generales** y **Por cliente**, la lista vuelve a la primera página. +- Al seleccionar otra sección, buscar o activar **Mostrar inactivas**, las tarifas vuelven a la primera página. +- Si se crean, desactivan o filtran registros y disminuye el número de páginas, la página actual se ajusta automáticamente. + +## Alcance + +No se modificaron tablas, políticas, permisos, consultas de Supabase, formularios de edición ni la lógica de creación/desactivación. El cambio se limita a la presentación paginada del módulo administrativo. + +## Distribución + +La carpeta `dist` ya contiene la versión lista para probar en XAMPP bajo `/tablero-cdc/`. diff --git a/README.md b/README.md index bffac08..addd302 100644 --- a/README.md +++ b/README.md @@ -1,282 +1,104 @@ -# Tablero CDC — Project Management +# Tablero CDC -> Aplicación interna de GomezLee Marketing para centralizar la gestión de proyectos creativos del CDC, sus aprobaciones, enlaces, estimación interna y tarifarios en una sola interfaz web. +Aplicación interna para la gestión visual de aprobaciones de proyectos creativos del equipo CDC de **GomezLee Marketing**. + +El objetivo de esta app es simplificar el flujo de trabajo del equipo creativo mediante un tablero tipo Trello, donde cada tarjeta representa un proyecto y centraliza sus datos principales, enlaces y estado de aprobación. --- -## INFORMACIÓN GENERAL +## Estado actual -| Campo | Detalle | -|---|---| -| **Proyecto** | Tablero CDC / CDC Project Management | -| **Área** | Creatividad y Diseño — CDC | -| **Developer Principal** | Isaac Aracena | -| **IT Manager** | Luis Matos | +Esta versión incluye: + +- Proyecto React + Vite limpio. +- Limpieza del ecosistema Lovable. +- Corrección de `County Manager` a `Country Manager`. +- Configuración de `base: "/tablero-cdc/"` en `vite.config.ts`. +- Favicon personalizado para la pestaña del navegador. +- Login corporativo con Supabase Auth y Google Sign-In. +- Restricción de acceso a correos `@gomezleemarketing.com`. +- Usuario autenticado real en el encabezado. +- Botón de cerrar sesión. +- Rol `isGerardo` para Gerardo Marrero. +- Permisos visuales iniciales: solo Gerardo Marrero puede ver/editar monto interno y editar estatus. +- Paginación local en las vistas de Todos, Activos y Cerrados con 12 proyectos por página. +- Persistencia de proyectos en Supabase. +- Sincronización multiusuario con Supabase Realtime. +- Desplegables dinámicos desde Supabase `tablero_cdc_app_lists`, con fallback local mientras se termina la sincronización del Sheet. --- -## OBJETIVO +## Funcionalidades principales -### Problema que resuelve - -La gestión de proyectos del CDC requiere centralizar información que de otro modo queda distribuida entre hojas de cálculo, enlaces, comunicaciones y seguimientos manuales. Esto dificulta consultar rápidamente el estado de un proyecto, sus responsables, propuestas, artes finales, costos internos y actividad reciente. - -### Solución implementada - -El Tablero CDC ofrece una aplicación web donde los usuarios autorizados pueden: - -- crear, consultar, editar y cerrar proyectos; -- filtrar y buscar proyectos; -- manejar links de brief, propuestas y artes finales; -- visualizar actividad e información clave de cada proyecto; -- calcular y guardar el costo interno mediante tarifarios; -- usar tarifarios generales o tarifarios especiales por cliente; -- administrar el catálogo de tarifarios cuando el usuario tiene permiso; -- consultar el total tarifado global o filtrado según permisos; -- trabajar con datos persistidos en Supabase y sincronizados entre usuarios. - -### Usuarios / Beneficiarios - -- Equipo de Creatividad y Diseño / CDC. -- Director Creativo. -- Usuarios internos autorizados de GomezLee Marketing. -- IT, para soporte, mantenimiento y administración técnica. -- Áreas que consumen la información consolidada posteriormente mediante Google Sheets / Power BI. +- Tablero visual de proyectos. +- Tarjetas por proyecto. +- Filtros por estado: Todos, Activos y Cerrados. +- Buscador por datos del proyecto. +- Modal de creación y edición de proyecto. +- Campos para cliente, marca, país, solicitante y `Country Manager`. +- Link de brief. +- Links múltiples de propuestas. +- Links múltiples de artes finales. +- Etiquetas de color por proyecto. +- Estatus visible para todos los usuarios. +- Monto interno visible solo para Gerardo Marrero. +- Paginación local para manejar tableros con muchos proyectos. --- -## ARQUITECTURA +## Funcionalidades pendientes -### Diagrama de flujo principal +Las siguientes funcionalidades se implementarán en próximas fases: -```text -Usuario autorizado - | - v -React + TypeScript + Vite - | - +--> Supabase Auth (Google OAuth) - | - +--> tablero_cdc_allowed_users - | | - | +--> permisos funcionales - | - +--> Supabase Postgres - | | - | +--> Proyectos - | +--> Links - | +--> Actividad - | +--> Costos internos - | +--> Listas dinámicas - | +--> Tarifarios - | +--> RPCs de paginación y totales - | - +--> Supabase Realtime - | - +--> n8n Webhook - | - v - Google Sheets - | - v - Power BI -``` - -### Flujo del tarifario - -```text -Google Sheet del tarifario - | - v - n8n - | - v -Supabase tariff catalog - | - +--> Secciones generales - | - +--> Tarifarios por cliente - | - v -Estimador interno del proyecto - | - v -tablero_cdc_project_pricing_items -``` - -Las tarifas administradas directamente desde la aplicación se identifican con `managed_by = 'app'` para protegerlas frente al sincronizador del Sheet. - -### Stack tecnológico - -| Componente | Tecnología | Propósito | -|---|---|---| -| Frontend | React 19 + TypeScript | Interfaz y lógica de la aplicación | -| Build / Dev Server | Vite 6 | Desarrollo y compilación | -| UI | Tailwind CSS 4 + Radix UI | Diseño y componentes | -| Base de datos | Supabase / PostgreSQL | Persistencia, RLS, RPCs y configuración | -| Autenticación | Supabase Auth + Google OAuth | Inicio de sesión corporativo | -| Tiempo real | Supabase Realtime | Sincronización multiusuario | -| Automatización | n8n | Integración con Google Sheets | -| Fuente / salida operativa | Google Sheets | Sincronización con procesos existentes | -| Reporting | Power BI | Consumo posterior de la información | -| Repositorio | Gitea | Control de versiones | - -### Integraciones externas - -| Sistema | Tipo de integración | Datos que fluyen | -|---|---|---| -| Supabase | SDK / REST / RPC / Realtime | Usuarios, proyectos, permisos, listas, links, actividad, tarifas y costos | -| Google OAuth | OAuth 2.0 vía Supabase | Identidad del usuario | -| n8n | Webhook HTTPS | Sincronización de proyectos y catálogo | -| Google Sheets | n8n | Datos operativos y catálogo de tarifarios | -| Power BI | Fuente existente basada en Sheets | Reporting / visualización | +1. Sincronización automática de la hoja `listas` del Google Sheet original hacia Supabase `tablero_cdc_app_lists`. +2. Escritura automática de datos en el Sheet original mediante n8n u otra integración definida. +3. Agregar una nueva columna final para enlace de brief en el Sheet original. +4. Reglas de seguridad definitivas con Supabase RLS. +5. Optimización futura de carga con RPC si el volumen de datos lo requiere. --- -## REGLAS DE NEGOCIO +## Arquitectura prevista -1. **El acceso no depende solamente del dominio del correo.** El usuario debe autenticarse con Google y existir activo en `public.tablero_cdc_allowed_users`. +```text +Frontend React/Vite + ↓ +Supabase Auth + ↓ +Supabase Database/Postgres + Realtime + RLS + ↓ +n8n como capa de integración + ↓ +Google Sheets original + ↓ +Power BI +``` -2. **Los permisos especiales se controlan desde Supabase**, principalmente mediante: - - `can_delete_projects` - - `can_manage_internal_pricing` - - `can_control_pricing_summary` - - `can_manage_tariff_catalog` - -3. **No se deben hardcodear administradores nuevos en el frontend.** Los accesos y permisos se administran en Supabase. - -4. **Los tarifarios generales** están disponibles para los clientes que no dependen de un tarifario especial. - -5. **Los tarifarios por cliente** solo aparecen al seleccionar el cliente asociado a esa sección. - -6. **Walmart Connect WMC** utiliza un tarifario especial con 12 piezas de precio fijo y mantiene la posibilidad de agregar un costo manual para propuestas o conceptos no contemplados. - -7. **Las tarifas administradas desde la app** quedan marcadas con `managed_by = 'app'`. El sincronizador del Sheet no debe sobrescribirlas. - -8. **Las tarifas históricas no deben eliminarse como operación normal.** Se deben desactivar para conservar integridad histórica. - -9. **Los costos seleccionados en un proyecto** se guardan en `tablero_cdc_project_pricing_items` y alimentan el total interno del proyecto. - -10. **El Total Tarifado Global** se calcula mediante la RPC `tablero_cdc_get_pricing_summary` y respeta los filtros activos. Los proyectos creados con tarifarios actuales o futuros siguen formando parte del total al guardar su costo interno. - -11. **La visibilidad del Total Tarifado** se controla mediante configuración y permisos de Supabase. - -12. **La estructura del Google Sheet original debe mantenerse estable**, especialmente cuando es consumida por Power BI. Cualquier cambio estructural debe validarse previamente. - -13. **Paginación actual:** - - proyectos: `24` por página; - - secciones del módulo de tarifario: `6` por página; - - tarifas / piezas del módulo: `10` por página. - -14. Al cambiar de sección, buscar, cambiar entre **Generales / Por cliente** o activar **Mostrar inactivas**, el módulo de tarifario reajusta la página automáticamente. +La app no debe modificar la estructura existente del Sheet original, ya que ese archivo alimenta un dashboard de Power BI. Cualquier columna nueva debe agregarse al final y validarse previamente. --- -## CONFIGURACIÓN Y SETUP +## Instalación local -### Prerrequisitos - -- Git. -- Node.js + npm. -- Acceso al repositorio interno en Gitea. -- Proyecto Supabase configurado. -- Google OAuth configurado en Supabase Auth. -- Acceso a SQL Editor de Supabase para instalaciones o migraciones. -- Acceso al workflow n8n correspondiente si se requiere sincronización con Google Sheets. - -### Variables de entorno - -Crear `.env` a partir de `.env.example`. - -| Variable | Descripción | Dónde se obtiene | -|---|---|---| -| `VITE_SUPABASE_URL` | URL pública del proyecto Supabase | Supabase / infraestructura GLM | -| `VITE_SUPABASE_ANON_KEY` | Anon/Public Key usada por el frontend | Supabase | -| `VITE_WEBHOOK_URL` | Webhook de sincronización de proyectos | n8n | - -Ejemplo: - -```env -VITE_SUPABASE_URL="https://dbit.digitalcompass.agency" -VITE_SUPABASE_ANON_KEY="TU_ANON_PUBLIC_KEY" -VITE_WEBHOOK_URL="https://agenteit.digitalcompass.agency/webhook/tablero-cdc-sync-proyecto" -``` - -> **Nunca commitear `.env`, `service_role`, secretos OAuth, contraseñas, tokens privados ni credenciales administrativas.** - -La `anon key` del frontend no debe sustituirse por una `service_role`. - -### Esquema de base de datos / SQL incluidos - -El repositorio mantiene scripts SQL separados para las distintas capacidades: - -```text -supabase_tablero_cdc_access_control.sql -supabase_tablero_cdc_alicia_permissions.sql -supabase_project_activity.sql -supabase_project_pricing_items.sql -supabase_realtime_projects.sql -supabase_rpc_projects_paginated.sql -supabase_pricing_summary_visibility.sql -supabase_tariff_catalog_and_safe_links.sql -supabase_walmart_connect_tariff.sql -supabase_tariff_admin_module.sql -``` - -#### Script principal del módulo de tarifario - -Para una instalación que ya tenga la base anterior del Tablero CDC, el archivo: - -```text -supabase_tariff_admin_module.sql -``` - -incorpora de forma idempotente el tarifario Walmart Connect y el módulo de administración del catálogo, incluyendo `can_manage_tariff_catalog`, las secciones dinámicas y sus políticas RLS. - -Después de ejecutarlo, verificar que los usuarios administradores esperados tengan: - -```text -can_manage_tariff_catalog = true -``` - -### Instalación local +Instalar dependencias: ```bash -git clone https://git.digitalcompass.agency/Isaac_Aracena/cdc-project-management.git -cd cdc-project-management npm install -cp .env.example .env ``` -Editar `.env` con los valores correctos. - -Ejecutar en desarrollo: +Ejecutar en modo desarrollo: ```bash npm run dev ``` -La configuración actual de Vite usa: - -```text -http://localhost:3000/tablero-cdc/ -``` - -### Build +Generar build de producción: ```bash npm run build ``` -El script ejecuta: - -```text -tsc && vite build -``` - -Por lo tanto, un error de TypeScript detiene el build y debe corregirse antes de publicar. - -### Preview +Vista previa del build: ```bash npm run preview @@ -284,352 +106,136 @@ npm run preview --- -## INSTALACIÓN / DEPLOY +## Variables de entorno -La aplicación está compilada para funcionar debajo de: +Crear un archivo `.env` local basado en `.env.example`. -```text -/tablero-cdc/ +Estructura esperada: + +```env +VITE_SUPABASE_URL="" +VITE_SUPABASE_ANON_KEY="" +VITE_WEBHOOK_URL="" ``` -Configuración en `vite.config.ts`: +El campo `VITE_WEBHOOK_URL` puede permanecer vacío hasta que se defina la integración con n8n. La `anon key` de Supabase es pública para el frontend; nunca subir claves privadas como `service_role`, secretos OAuth o contraseñas de base de datos. + +--- + +## Seguridad + +No se deben subir al repositorio: + +- `.env` +- `.env.local` +- `.env.production` +- `.env.development` +- `node_modules` +- `dist` +- tokens +- credenciales +- archivos con secretos +- logs innecesarios + +El archivo `.env.example` sí puede subirse, siempre que no contenga valores reales. + +--- + +## Despliegue + +El proyecto está preparado para publicarse bajo la ruta: ```ts base: "/tablero-cdc/"; ``` -### Deploy manual mediante `dist` +Si la ruta final cambia, debe actualizarse `vite.config.ts` y el `basename` del router si aplica. -Este repositorio **sí mantiene el `dist` validado** porque el flujo actual de despliegue utiliza esa carpeta directamente en el servidor. +--- -El `dist` debe contener como mínimo: +## Notas importantes + +- Este repositorio representa la base inicial del proyecto. +- El diseño base fue aprobado antes de iniciar las integraciones técnicas. +- Las próximas fases deben implementarse en commits separados. +- No se debe alterar la estructura del Google Sheet original sin validación previa. +- La integración con Power BI depende de mantener estable la estructura del Sheet. + +--- + +## Equipo + +Proyecto interno de **GomezLee Marketing**. + +Desarrollo y soporte técnico: **Isaac Aracena**. + +## Persistencia en Supabase + +Esta versión ya no depende de `localStorage` para los proyectos. El tablero lee y guarda la información en Supabase usando las tablas: + +- `tablero_cdc_projects` +- `tablero_cdc_project_links` +- `tablero_cdc_profiles` +- `tablero_cdc_app_lists` + +Notas importantes: + +- Los proyectos abiertos se guardan en base de datos con `status = 'Activo'`, pero en la interfaz se muestran como proyectos sin estatus para mantener el comportamiento visual original. +- Los usuarios normales no ven ni actualizan el monto interno desde la app. +- Solo Gerardo Marrero puede modificar estatus, monto interno y eliminar proyectos desde la interfaz. +- Los links de propuestas y artes finales se guardan en `tablero_cdc_project_links`. +- Los desplegables se leen desde `tablero_cdc_app_lists`. Si una categoría todavía no existe en Supabase, la app usa una lista local de respaldo para no bloquear el formulario. + +## Supabase Realtime + +Para que la sincronización multiusuario funcione, habilita Realtime para las tablas `tablero_cdc_projects`, `tablero_cdc_project_links` y `tablero_cdc_app_lists` ejecutando en Supabase SQL Editor el archivo: ```text -dist/ -├── assets/ -├── index.html -├── favicon.ico -└── demás archivos públicos generados +supabase_realtime_projects.sql ``` -> `dist/assets/` es obligatorio. Sin esa carpeta, `index.html` no podrá cargar correctamente el JavaScript y CSS compilados. +Luego abre la app en dos pestañas o dos navegadores: al crear, editar o eliminar un proyecto en una ventana, la otra debe actualizarse automáticamente. -Flujo recomendado: +## Desplegables dinámicos -1. Probar la aplicación con `npm run dev`. -2. Generar el build con `npm run build`. -3. Probar **ese mismo `dist`** en XAMPP bajo `/tablero-cdc/`. -4. No regenerar el build después de la validación si se desea desplegar exactamente la versión probada. -5. Subir a Gitea el mismo `dist`, incluyendo `dist/assets/`. -6. Copiar ese `dist` validado al servidor. -7. Probar login, tablero, tarifario y una apertura directa sin recargar la página. +La app intenta cargar estos desplegables desde `public.tablero_cdc_app_lists`: ---- +- `client` / `cliente` / `clientes` +- `brand` / `marca` / `marcas` +- `country` / `pais` / `bu` +- `status` / `estatus` +- `country_manager` / `cm` / `bu_cm` para mapear BU → Country Manager -## CÓMO FUNCIONA +Si una categoría aún no existe en Supabase, se usa el respaldo local del frontend. Más adelante n8n sincronizará la hoja `listas` del Sheet original hacia `tablero_cdc_app_lists`. -### Flujo paso a paso +## Actualización: Tarifario dinámico desde Sheet/n8n, permisos internos y total tarifado por RPC -1. El usuario entra a la aplicación. -2. Supabase Auth inicia o recupera la sesión Google. -3. La app consulta `tablero_cdc_allowed_users`. -4. Si el usuario no está activo/autorizado, la sesión se rechaza. -5. Si está autorizado, la app carga sus permisos. -6. El tablero consulta los proyectos desde Supabase mediante RPC paginada. -7. Los filtros se aplican desde la consulta y no requieren descargar toda la base. -8. Realtime mantiene sincronizadas las ventanas activas. -9. Al crear o editar un proyecto, la información se persiste en Supabase. -10. Si `VITE_WEBHOOK_URL` está configurado, la aplicación dispara la sincronización correspondiente hacia n8n. -11. Los costos internos se guardan como líneas de pricing por proyecto. -12. El total tarifado se calcula en Supabase mediante RPC. -13. El tarifario administrativo solo aparece a usuarios con `can_manage_tariff_catalog = true`. +Esta versión mantiene la app como consumidora del tarifario, pero elimina la administración manual dentro de la app: -### Tarifario general +1. **Tarifario desde Google Sheets/n8n**: ya no aparece la pantalla privada de “Administrar tarifario”. El catálogo visible en los proyectos se carga desde `tablero_cdc_tariff_catalog`, que deberá sincronizarse desde el Google Sheet del tarifario mediante n8n. La app conserva el tarifario base como respaldo si la tabla está vacía o aún no existe. +2. **Total tarifado global/filtrado por RPC**: la tarjeta de total tarifado ya no necesita traer todos los proyectos para sumar en el navegador. La app llama la RPC `tablero_cdc_get_pricing_summary`, que calcula en Supabase el total de `Interno Cargado` y la cantidad de proyectos incluidos según los filtros activos. Si no hay filtros, devuelve el total global. +3. **Permisos internos ajustados**: el total tarifado, `Interno Cargado` y eliminar proyecto quedan restringidos en frontend a `gmarrero@gomezleemarketing.com` e `iaracena@gomezleemarketing.com`. -El estimador puede utilizar secciones generales como: +Antes de usar esta versión en producción, ejecutar en Supabase SQL Editor: -- **Tarifario Gráfico CDC** -- **Estrategia y Creatividad** - -Cada tarifa puede incluir categoría, servicio, tipo de trabajo, nivel, rango de referencia, notas y orden. - -### Tarifario por cliente - -Las secciones con `scope = 'client'` se muestran únicamente cuando el proyecto tiene seleccionado el cliente asociado. - -Ejemplo actual: - -```text -Walmart Connect WMC +```sql +-- Archivo incluido en este ZIP: +-- supabase_tariff_catalog_and_safe_links.sql ``` -El usuario puede seleccionar varias piezas y el subtotal se calcula automáticamente. +Ese SQL crea/actualiza `tablero_cdc_tariff_catalog`, la función transaccional `tablero_cdc_replace_project_links`, la RPC `tablero_cdc_get_pricing_summary` y actualiza `tablero_cdc_get_projects_paginated` para que el monto interno solo viaje a Marrero e Isaac. -### Administración del tarifario +## Tarifario fijo Walmart Connect -Los usuarios autorizados pueden: +Esta versión incluye el tarifario específico de Walmart Connect basado en `Tarifario_XCDC_WMC.xlsx`. -- crear secciones generales; -- crear tarifarios por cliente; -- crear tarifas / piezas; -- editar secciones y tarifas; -- desactivar y reactivar; -- buscar; -- mostrar inactivas; -- paginar listas extensas. +- Al seleccionar `Walmart Connect WMC` como cliente, la estimación interna muestra únicamente: + - selección múltiple de piezas WMC con montos fijos; + - costo/propuesta manual WMC para casos cotizados por proyecto. +- Para los demás clientes se conservan sin cambios el Tarifario Gráfico CDC, Estrategia y Creatividad y el costo manual. +- Las piezas seleccionadas se totalizan automáticamente y se guardan en `tablero_cdc_project_pricing_items` como líneas normales del tarifario. +- Antes de desplegar, ejecutar `supabase_walmart_connect_tariff.sql` en Supabase SQL Editor. -La administración no debe borrar costos ya guardados en proyectos históricos. - -### Schedules / Triggers - -| Trigger | Frecuencia | Descripción | -|---|---|---| -| Interacción del usuario | On demand | Crear, editar, filtrar o cotizar proyectos | -| Webhook de proyecto | On demand | La app envía cambios a n8n cuando `VITE_WEBHOOK_URL` está configurado | -| Realtime Supabase | Evento | Actualiza la app cuando cambian tablas suscritas | -| Sync de tarifario Sheet → Supabase | Según workflow n8n | Mantiene actualizado el catálogo administrado desde Sheet | - -> La frecuencia exacta del sincronizador n8n debe consultarse en el workflow activo; no está definida por el frontend. - ---- - -## TESTING - -### Casos de prueba mínimos - -| Caso | Input / Acción | Output esperado | Estado | -|---|---|---|---| -| Login autorizado | Usuario activo en `tablero_cdc_allowed_users` | Entra al tablero | Revalidar tras deploy | -| Login no autorizado | Usuario sin fila activa | Acceso rechazado | Revalidar tras cambios de acceso | -| Carga del tablero | Abrir Todos / Activos / Cerrados | Proyectos paginados correctamente | Revalidar tras deploy | -| Filtros | País, marca, cliente o CM | Resultado y total corresponden al filtro | Revalidar tras cambios SQL | -| Realtime | Dos ventanas abiertas | Cambios visibles sin recarga manual | Revalidar tras cambios de Realtime | -| Links | Editar propuestas / artes finales | Persisten al reabrir el proyecto | Revalidar tras cambios en store/RPC | -| Tarifario general | Cliente normal | Secciones generales disponibles | Revalidar tras cambios de catálogo | -| Walmart Connect | Cliente `Walmart Connect WMC` | Solo tarifario especial correspondiente + costo manual | Validado funcionalmente | -| WMC multiselección | Elegir varias piezas | Subtotal correcto | Validado funcionalmente | -| Administrar tarifario | Usuario con `can_manage_tariff_catalog = true` | Botón Tarifario visible y módulo accesible | Validado funcionalmente | -| Usuario sin permiso | `can_manage_tariff_catalog = false` | No ve módulo administrativo | Revalidar al modificar permisos | -| Paginación secciones | Más de 6 secciones | Controles de página sin perder selección | Implementado | -| Paginación tarifas | Más de 10 tarifas | Controles de página correctos | Implementado | -| Mostrar inactivas | Activar switch | Se muestran registros desactivados cuando existan | Implementado | -| Total tarifado | Crear/editar costos | Total global/filtrado se actualiza | Revalidar tras cambios de pricing | -| Build | `npm run build` | `tsc && vite build` sin errores | Requerido antes de generar nuevo dist | -| XAMPP | Abrir `dist` en `/tablero-cdc/` | App y módulo Tarifario abren sin recarga | Validado en la versión actual | - -### Prueba de referencia Walmart Connect - -La documentación técnica incluida define este caso: - -- `Uniformes` -- `Photobooth` -- `Arco de entrada` - -Subtotal esperado: - -```text -$480.00 -``` - -Agregando manualmente: - -```text -Propuesta general Walmart Connect = $900.00 -``` - -Total esperado: - -```text -$1,380.00 -``` - -Al guardar, cerrar y volver a abrir el proyecto, las líneas y el total deben persistir. - ---- - -## ERRORES CONOCIDOS Y TROUBLESHOOTING - -| Error / Síntoma | Causa probable | Solución | -|---|---|---| -| Pantalla en blanco al publicar | Falta `dist/assets` o las rutas del build no coinciden | Confirmar `dist/assets/` y `base: "/tablero-cdc/"` | -| Tarifario abre como modal blanco hasta recargar | Build antiguo / híbrido o assets desactualizados | Generar un build limpio desde el código fuente actual y desplegar exactamente el `dist` probado | -| `npm run build` falla en TypeScript | Error de tipos antes de ejecutar Vite | Corregir el error de `tsc`; no publicar un dist nuevo hasta que el build termine correctamente | -| Botón **Tarifario** no aparece | Falta permiso o SQL del módulo | Verificar `can_manage_tariff_catalog = true` y recargar sesión | -| Usuario válido no puede entrar | No existe como activo en `tablero_cdc_allowed_users` | Revisar fila, correo normalizado e `is_active` | -| Error / ausencia de RPC paginada | SQL no aplicado o schema cache desactualizado | Ejecutar `supabase_rpc_projects_paginated.sql` y revisar Supabase | -| Total tarifado no responde como esperado | RPC/configuración de visibilidad no aplicada | Revisar `supabase_pricing_summary_visibility.sql` y `tablero_cdc_get_pricing_summary` | -| Tarifario no carga desde Supabase | Tabla / SQL no aplicado | Revisar `tablero_cdc_tariff_catalog` y `tablero_cdc_tariff_sections` | -| Sync a Sheet no ocurre | `VITE_WEBHOOK_URL` vacío o workflow n8n inactivo | Revisar `.env`, webhook y ejecución de n8n | -| Cambios no aparecen en otra ventana | Realtime no habilitado | Ejecutar / revisar `supabase_realtime_projects.sql` | -| OAuth vuelve a una ruta incorrecta | Redirect URL no autorizada o base incorrecta | Revisar configuración OAuth y `/tablero-cdc/` | - ---- - -## MONITOREO - -- **Supabase:** revisar errores de Auth, RLS, RPC y consultas. -- **n8n Executions:** revisar ejecuciones fallidas del webhook y sincronizadores. -- **Browser DevTools:** revisar `Console` y `Network` ante errores de frontend o `404`. -- **Gitea:** confirmar que el commit de despliegue contiene `dist/index.html` y `dist/assets/`. -- **Prueba funcional:** abrir la app en una sesión limpia después de cada despliegue. - -### Output esperado en operación normal - -- usuarios autorizados ingresan con Google; -- usuarios no autorizados quedan fuera; -- proyectos cargan paginados; -- cambios persisten en Supabase; -- Realtime mantiene sincronización multiusuario; -- tarifarios muestran únicamente las secciones aplicables; -- costos guardados alimentan el total interno y el resumen global; -- el módulo administrativo solo aparece a quienes tienen permiso; -- el build publicado funciona directamente sin requerir recargar la página. - ---- - -## ESTRUCTURA DEL REPOSITORIO - -```text -cdc-project-management/ -├── dist/ # Build probado para despliegue -│ ├── assets/ # JS/CSS compilado — obligatorio -│ └── index.html -├── public/ # Favicons y assets públicos -├── src/ -│ ├── components/ -│ │ ├── board/ # Tarjetas, diálogo y pricing -│ │ ├── tariff/ # Administración del tarifario -│ │ └── ui/ # Componentes de interfaz -│ ├── context/ -│ │ └── AuthContext.tsx # Sesión y permisos -│ ├── data/ # Datos de respaldo -│ ├── hooks/ -│ ├── lib/ -│ │ ├── accessControl.ts -│ │ ├── appLists.ts -│ │ ├── pricingSummaryVisibility.ts -│ │ ├── store.ts -│ │ ├── supabase.ts -│ │ ├── tariffCatalog.ts -│ │ └── tariffSections.ts -│ ├── pages/ -│ │ └── BoardPage.tsx -│ ├── App.tsx -│ ├── main.tsx -│ └── styles.css -├── .env.example -├── package.json -├── package-lock.json -├── vite.config.ts -├── tsconfig.json -├── supabase_*.sql # Migraciones / configuración -├── MODULO_TARIFARIO_ADMIN_IMPLEMENTACION.md -├── PAGINACION_MODULO_TARIFARIO.md -├── WMC_TARIFARIO_IMPLEMENTACION.md -├── VALIDACION_MODULO_TARIFARIO.md -├── CORRECCION_BUILD_Y_MODAL_TARIFARIO.md -└── README.md -``` - ---- - -## SEGURIDAD - -### No commitear - -```text -.env -.env.local -.env.production -service_role keys -tokens privados -secretos OAuth -contraseñas -node_modules/ -logs con información sensible -``` - -### Sí se mantiene en este repositorio - -```text -.env.example -dist/ -dist/assets/ -scripts SQL versionados -documentación técnica -``` - -La inclusión de `dist/` es intencional mientras el procedimiento de producción dependa de desplegar exactamente el build probado en XAMPP. - ---- - -## CHANGELOG - -### 2026-08-07 — Documentación - -- README actualizado al estándar GLM IT. -- Se documenta arquitectura, permisos, setup, deploy, testing y troubleshooting. -- Se deja explícito que `dist/assets/` forma parte obligatoria del build desplegable. - -### 2026-07-28 — Módulo de tarifario administrativo - -- Incorporación del tarifario especial Walmart Connect. -- Administración de secciones generales y por cliente. -- Permiso `can_manage_tariff_catalog`. -- Creación, edición, desactivación y reactivación de tarifas. -- Compatibilidad con tarifas gestionadas por Sheet y por app. -- Corrección del build / renderizado del modal del tarifario. -- Paginación de secciones y tarifas: - - 6 secciones por página. - - 10 tarifas por página. -- `dist` validado mediante XAMPP bajo `/tablero-cdc/`. - ---- - -## DECISIONS LOG - -### DEC-001 — Acceso administrado desde Supabase - -- **Contexto:** evitar listas rígidas de usuarios dentro del frontend. -- **Decisión:** utilizar `tablero_cdc_allowed_users`. -- **Razón:** permite habilitar, deshabilitar y asignar permisos sin recompilar la aplicación. - -### DEC-002 — Permisos granulares - -- **Contexto:** no todos los usuarios deben administrar costos, eliminar proyectos, controlar el resumen o editar tarifarios. -- **Decisión:** usar flags independientes en Supabase. -- **Razón:** mantener privilegio mínimo y separar responsabilidades. - -### DEC-003 — Tarifario híbrido Sheet + App - -- **Contexto:** las tarifas generales existentes se mantienen desde el proceso operativo, mientras nuevos tarifarios especiales pueden administrarse desde la app. -- **Decisión:** utilizar `managed_by` para distinguir origen y proteger registros administrados en la aplicación. -- **Razón:** evitar que el sincronizador del Sheet sobrescriba cambios creados desde el módulo administrativo. - -### DEC-004 — Desactivar en lugar de eliminar tarifas históricas - -- **Contexto:** una tarifa puede estar referenciada por proyectos anteriores. -- **Decisión:** usar `is_active = false` como operación habitual. -- **Razón:** conservar trazabilidad e integridad histórica. - -### DEC-005 — Base de Vite fija en `/tablero-cdc/` - -- **Contexto:** la aplicación se publica dentro de una subcarpeta. -- **Decisión:** configurar `base: "/tablero-cdc/"`. -- **Razón:** generar rutas correctas para JS, CSS y assets en producción. - -### DEC-006 — Publicar exactamente el `dist` validado - -- **Contexto:** el build de producción debe comportarse igual que la versión probada antes del despliegue. -- **Decisión:** validar el `dist` en XAMPP y subir ese mismo build al repositorio / servidor. -- **Razón:** evitar diferencias entre el artefacto probado y el artefacto publicado. - ---- - -## CONTACTOS DEL PROYECTO - -| Rol | Nombre | Contacto | -|---|---|---| -| IT Manager | Luis Matos | `lmatos@gomezleemarketing.com` | -| Developer Principal | Isaac Aracena | `iaracena@gomezleemarketing.com` | +## Módulo de administración del tarifario +La versión incluye un módulo de tarifario controlado por el permiso de Supabase `can_manage_tariff_catalog`. Permite editar las secciones generales, crear nuevas secciones y crear tarifarios especiales por cliente. Consulta `MODULO_TARIFARIO_ADMIN_IMPLEMENTACION.md` y ejecuta `supabase_tariff_admin_module.sql` antes del despliegue. diff --git a/REAPROBAR_BRIEFS_RECHAZADOS.md b/REAPROBAR_BRIEFS_RECHAZADOS.md new file mode 100644 index 0000000..2abc23f --- /dev/null +++ b/REAPROBAR_BRIEFS_RECHAZADOS.md @@ -0,0 +1,25 @@ +# Reaprobar briefs rechazados + +Cambio puntual agregado sobre la versión con **Nuevos**, **Rechazados** y notificaciones por usuario. + +## Comportamiento + +- Un brief en `Nuevos` puede seguir siendo **Aprobado** o **Rechazado**. +- Un brief en `Rechazados` ahora muestra el botón **Aprobar**. +- Al aprobarlo desde `Rechazados`: + - se crea un proyecto normal en `tablero_cdc_projects`; + - el proyecto se crea con estado `Activo`; + - aparece en `Todos` y `Activos`; + - el brief desaparece de `Rechazados`; + - se limpia `rejection_reason`; + - se conserva la misma sincronización existente hacia n8n/Google Sheet. +- La RPC mantiene idempotencia para evitar duplicados por doble clic o reintentos. +- No se modifican los workflows de n8n. + +## Supabase + +Si la bandeja de briefs ya estaba instalada, ejecutar una vez: + +`supabase_cdc_brief_reapprove_rejected.sql` + +El archivo completo `supabase_cdc_brief_review_inbox.sql` también quedó actualizado para instalaciones nuevas. diff --git a/TIEMPO_PROYECTO_IMPLEMENTACION.md b/TIEMPO_PROYECTO_IMPLEMENTACION.md new file mode 100644 index 0000000..03ed61e --- /dev/null +++ b/TIEMPO_PROYECTO_IMPLEMENTACION.md @@ -0,0 +1,38 @@ +# Tiempo dedicado por proyecto — implementación + +## Qué agrega + +- Botón **Tiempo** en cada tarjeta de proyecto. +- Modal independiente para registrar y consultar tiempo sin abrir/editar el proyecto. +- Registro por **país**, **tarea**, **horas/minutos**, **fecha** y **nota opcional**. +- Resumen automático: + - total del proyecto; + - desglose por país; + - desglose por tarea. +- Historial detallado con usuario y fecha de registro. +- Eliminación de registros con confirmación para corregir capturas equivocadas. +- Paginación del historial (10 registros por página). + +## Antes de desplegar + +Ejecutar en Supabase SQL Editor: + +```text +supabase_project_time_entries.sql +``` + +El script es idempotente y no modifica las tablas de proyectos, tarifarios ni montos. + +## Seguridad + +Solo usuarios autenticados que estén activos en `tablero_cdc_allowed_users` pueden leer o modificar registros de tiempo. + +## Persistencia + +Tabla nueva: + +```text +public.tablero_cdc_project_time_entries +``` + +La duración se guarda en minutos enteros para evitar redondeos. diff --git a/VALIDACION_MODULO_TARIFARIO.md b/VALIDACION_MODULO_TARIFARIO.md new file mode 100644 index 0000000..4764198 --- /dev/null +++ b/VALIDACION_MODULO_TARIFARIO.md @@ -0,0 +1,42 @@ +# Validación final — módulo Tarifario + +Fecha: 28 de julio de 2026 + +## Correcciones aplicadas + +1. Se corrigió el error `TS2322` de `src/lib/tariffCatalog.ts` que impedía ejecutar `npm run build`. + - La respuesta de la consulta moderna y la consulta de compatibilidad ahora comparten explícitamente el tipo `Array> | null`. + - No se alteró la lógica de lectura, mezcla, respaldo ni guardado del catálogo. + +2. Se corrigió el cuadro blanco del tarifario en el `dist`. + - El lanzador permanece junto al botón **Nuevo**. + - El modal ahora se renderiza mediante un portal directamente en `document.body`. + - Esto evita que el `header` sticky con `backdrop-filter` limite, recorte o altere el modal en producción/XAMPP. + +3. Se generaron nombres de assets nuevos para evitar reutilizar JavaScript antiguo del caché: + - `tariff-admin-module-ready.js` + - `index-core-tariff-admin-ready.js` + - `index-ready.css` + +4. Se conservaron alias compatibles con los nombres anteriores para evitar errores 404 si algún navegador conserva temporalmente un `index.html` anterior. + +## Pruebas realizadas + +- 74 archivos TypeScript/TSX transpilados: **0 errores de sintaxis**. +- Importaciones locales verificadas: **0 rutas faltantes**. +- JavaScript del `dist` validado con `node --check`: **correcto**. +- Prueba automatizada en Chromium con un encabezado sticky y `backdrop-filter`, equivalente al encabezado real: + - botón **Tarifario** visible con permiso activo; + - modal insertado directamente dentro de `BODY`; + - overlay cubriendo el viewport completo; + - tres ciclos consecutivos de abrir, cerrar y volver a abrir; + - secciones generales visibles; + - tarifarios por cliente visibles; + - editor de una nueva tarifa abierto y cerrado correctamente; + - **0 errores JavaScript durante la prueba**. + +## Estado del paquete + +El directorio `dist` incluido está listo para copiar dentro de `htdocs/tablero-cdc` o desplegar en el servidor bajo la ruta `/tablero-cdc/`. + +El registro npm disponible en el entorno de preparación respondió `503 Service Temporarily Unavailable`, por lo que no fue posible reinstalar todas las dependencias y repetir aquí un build completo desde cero. No obstante, el error TypeScript exacto reportado quedó corregido en el código fuente y el `dist` incluido fue corregido y probado directamente en Chromium. diff --git a/WMC_TARIFARIO_IMPLEMENTACION.md b/WMC_TARIFARIO_IMPLEMENTACION.md new file mode 100644 index 0000000..842c426 --- /dev/null +++ b/WMC_TARIFARIO_IMPLEMENTACION.md @@ -0,0 +1,25 @@ +# Tablero CDC — Tarifario Walmart Connect + +## Comportamiento + +- Cliente `Walmart Connect WMC`: + - muestra selección múltiple de las 12 piezas WMC; + - cada pieza usa un monto fijo y se totaliza automáticamente; + - mantiene un bloque manual para propuestas generales o conceptos no contemplados. +- Cualquier otro cliente: + - mantiene sin cambios `Tarifario Gráfico CDC`; + - mantiene sin cambios `Estrategia y Creatividad`; + - mantiene el costo manual existente. + +## Instalación + +1. Ejecutar `supabase_walmart_connect_tariff.sql` en Supabase SQL Editor. +2. Confirmar que el primer resultado muestre `12` piezas activas y `2180.00` como suma de referencia. +3. Desplegar el contenido de `dist` incluido. +4. Probar un proyecto con cliente `Walmart Connect WMC` y otro con un cliente normal. + +## Prueba mínima + +- Seleccionar `Uniformes`, `Photobooth` y `Arco de entrada` debe producir un subtotal WMC de `$480.00`. +- Agregar manualmente `Propuesta general Walmart Connect` por `$900.00` debe producir un total de `$1,380.00`. +- Guardar, cerrar y volver a abrir el proyecto debe conservar las cuatro líneas y el total. diff --git a/dist/assets/index-COXhoke_.js b/dist/assets/index-COXhoke_.js deleted file mode 100644 index aa12d95..0000000 --- a/dist/assets/index-COXhoke_.js +++ /dev/null @@ -1,433 +0,0 @@ -function FR(e,t){for(var r=0;rs[i]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))s(i);new MutationObserver(i=>{for(const l of i)if(l.type==="childList")for(const u of l.addedNodes)u.tagName==="LINK"&&u.rel==="modulepreload"&&s(u)}).observe(document,{childList:!0,subtree:!0});function r(i){const l={};return i.integrity&&(l.integrity=i.integrity),i.referrerPolicy&&(l.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?l.credentials="include":i.crossOrigin==="anonymous"?l.credentials="omit":l.credentials="same-origin",l}function s(i){if(i.ep)return;i.ep=!0;const l=r(i);fetch(i.href,l)}})();function y_(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Cm={exports:{}},il={};/** - * @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 dx;function KR(){if(dx)return il;dx=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.fragment");function r(s,i,l){var u=null;if(l!==void 0&&(u=""+l),i.key!==void 0&&(u=""+i.key),"key"in i){l={};for(var d in i)d!=="key"&&(l[d]=i[d])}else l=i;return i=l.ref,{$$typeof:e,type:s,key:u,ref:i!==void 0?i:null,props:l}}return il.Fragment=t,il.jsx=r,il.jsxs=r,il}var fx;function YR(){return fx||(fx=1,Cm.exports=KR()),Cm.exports}var m=YR(),Tm={exports:{}},Ue={};/** - * @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 hx;function WR(){if(hx)return Ue;hx=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.portal"),r=Symbol.for("react.fragment"),s=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),l=Symbol.for("react.consumer"),u=Symbol.for("react.context"),d=Symbol.for("react.forward_ref"),f=Symbol.for("react.suspense"),h=Symbol.for("react.memo"),b=Symbol.for("react.lazy"),g=Symbol.for("react.activity"),x=Symbol.iterator;function _(k){return k===null||typeof k!="object"?null:(k=x&&k[x]||k["@@iterator"],typeof k=="function"?k:null)}var E={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},S=Object.assign,T={};function C(k,O,U){this.props=k,this.context=O,this.refs=T,this.updater=U||E}C.prototype.isReactComponent={},C.prototype.setState=function(k,O){if(typeof k!="object"&&typeof k!="function"&&k!=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,k,O,"setState")},C.prototype.forceUpdate=function(k){this.updater.enqueueForceUpdate(this,k,"forceUpdate")};function R(){}R.prototype=C.prototype;function A(k,O,U){this.props=k,this.context=O,this.refs=T,this.updater=U||E}var D=A.prototype=new R;D.constructor=A,S(D,C.prototype),D.isPureReactComponent=!0;var B=Array.isArray;function I(){}var L={H:null,A:null,T:null,S:null},H=Object.prototype.hasOwnProperty;function Z(k,O,U){var Y=U.ref;return{$$typeof:e,type:k,key:O,ref:Y!==void 0?Y:null,props:U}}function ue(k,O){return Z(k.type,O,k.props)}function ce(k){return typeof k=="object"&&k!==null&&k.$$typeof===e}function he(k){var O={"=":"=0",":":"=2"};return"$"+k.replace(/[=:]/g,function(U){return O[U]})}var de=/\/+/g;function me(k,O){return typeof k=="object"&&k!==null&&k.key!=null?he(""+k.key):O.toString(36)}function pe(k){switch(k.status){case"fulfilled":return k.value;case"rejected":throw k.reason;default:switch(typeof k.status=="string"?k.then(I,I):(k.status="pending",k.then(function(O){k.status==="pending"&&(k.status="fulfilled",k.value=O)},function(O){k.status==="pending"&&(k.status="rejected",k.reason=O)})),k.status){case"fulfilled":return k.value;case"rejected":throw k.reason}}throw k}function j(k,O,U,Y,$){var re=typeof k;(re==="undefined"||re==="boolean")&&(k=null);var te=!1;if(k===null)te=!0;else switch(re){case"bigint":case"string":case"number":te=!0;break;case"object":switch(k.$$typeof){case e:case t:te=!0;break;case b:return te=k._init,j(te(k._payload),O,U,Y,$)}}if(te)return $=$(k),te=Y===""?"."+me(k,0):Y,B($)?(U="",te!=null&&(U=te.replace(de,"$&/")+"/"),j($,O,U,"",function(Se){return Se})):$!=null&&(ce($)&&($=ue($,U+($.key==null||k&&k.key===$.key?"":(""+$.key).replace(de,"$&/")+"/")+te)),O.push($)),1;te=0;var ee=Y===""?".":Y+":";if(B(k))for(var se=0;se>>1,G=j[J];if(0>>1;Ji(U,W))Yi($,U)?(j[J]=$,j[Y]=W,J=Y):(j[J]=U,j[O]=W,J=O);else if(Yi($,W))j[J]=$,j[Y]=W,J=Y;else break e}}return K}function i(j,K){var W=j.sortIndex-K.sortIndex;return W!==0?W:j.id-K.id}if(e.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var l=performance;e.unstable_now=function(){return l.now()}}else{var u=Date,d=u.now();e.unstable_now=function(){return u.now()-d}}var f=[],h=[],b=1,g=null,x=3,_=!1,E=!1,S=!1,T=!1,C=typeof setTimeout=="function"?setTimeout:null,R=typeof clearTimeout=="function"?clearTimeout:null,A=typeof setImmediate<"u"?setImmediate:null;function D(j){for(var K=r(h);K!==null;){if(K.callback===null)s(h);else if(K.startTime<=j)s(h),K.sortIndex=K.expirationTime,t(f,K);else break;K=r(h)}}function B(j){if(S=!1,D(j),!E)if(r(f)!==null)E=!0,I||(I=!0,he());else{var K=r(h);K!==null&&pe(B,K.startTime-j)}}var I=!1,L=-1,H=5,Z=-1;function ue(){return T?!0:!(e.unstable_now()-Zj&&ue());){var J=g.callback;if(typeof J=="function"){g.callback=null,x=g.priorityLevel;var G=J(g.expirationTime<=j);if(j=e.unstable_now(),typeof G=="function"){g.callback=G,D(j),K=!0;break t}g===r(f)&&s(f),D(j)}else s(f);g=r(f)}if(g!==null)K=!0;else{var k=r(h);k!==null&&pe(B,k.startTime-j),K=!1}}break e}finally{g=null,x=W,_=!1}K=void 0}}finally{K?he():I=!1}}}var he;if(typeof A=="function")he=function(){A(ce)};else if(typeof MessageChannel<"u"){var de=new MessageChannel,me=de.port2;de.port1.onmessage=ce,he=function(){me.postMessage(null)}}else he=function(){C(ce,0)};function pe(j,K){L=C(function(){j(e.unstable_now())},K)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(j){j.callback=null},e.unstable_forceFrameRate=function(j){0>j||125J?(j.sortIndex=W,t(h,j),r(f)===null&&j===r(h)&&(S?(R(L),L=-1):S=!0,pe(B,W-J))):(j.sortIndex=G,t(f,j),E||_||(E=!0,I||(I=!0,he()))),j},e.unstable_shouldYield=ue,e.unstable_wrapCallback=function(j){var K=x;return function(){var W=x;x=K;try{return j.apply(this,arguments)}finally{x=W}}}})(Rm)),Rm}var gx;function JR(){return gx||(gx=1,Am.exports=XR()),Am.exports}var jm={exports:{}},dn={};/** - * @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 vx;function QR(){if(vx)return dn;vx=1;var e=jd();function t(f){var h="https://react.dev/errors/"+f;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(t){console.error(t)}}return e(),jm.exports=QR(),jm.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 bx;function ZR(){if(bx)return ol;bx=1;var e=JR(),t=jd(),r=b_();function s(n){var a="https://react.dev/errors/"+n;if(1G||(n.current=J[G],J[G]=null,G--)}function U(n,a){G++,J[G]=n.current,n.current=a}var Y=k(null),$=k(null),re=k(null),te=k(null);function ee(n,a){switch(U(re,a),U($,n),U(Y,null),a.nodeType){case 9:case 11:n=(n=a.documentElement)&&(n=n.namespaceURI)?D0(n):0;break;default:if(n=a.tagName,a=a.namespaceURI)a=D0(a),n=P0(a,n);else switch(n){case"svg":n=1;break;case"math":n=2;break;default:n=0}}O(Y),U(Y,n)}function se(){O(Y),O($),O(re)}function Se(n){n.memoizedState!==null&&U(te,n);var a=Y.current,o=P0(a,n.type);a!==o&&(U($,n),U(Y,o))}function Te(n){$.current===n&&(O(Y),O($)),te.current===n&&(O(te),nl._currentValue=W)}var Re,nt;function Ze(n){if(Re===void 0)try{throw Error()}catch(o){var a=o.stack.trim().match(/\n( *(at )?)/);Re=a&&a[1]||"",nt=-1)":-1p||M[c]!==V[p]){var ne=` -`+M[c].replace(" at new "," at ");return n.displayName&&ne.includes("")&&(ne=ne.replace("",n.displayName)),ne}while(1<=c&&0<=p);break}}}finally{xt=!1,Error.prepareStackTrace=o}return(o=n?n.displayName||n.name:"")?Ze(o):""}function je(n,a){switch(n.tag){case 26:case 27:case 5:return Ze(n.type);case 16:return Ze("Lazy");case 13:return n.child!==a&&a!==null?Ze("Suspense Fallback"):Ze("Suspense");case 19:return Ze("SuspenseList");case 0:case 15:return oe(n.type,!1);case 11:return oe(n.type.render,!1);case 1:return oe(n.type,!0);case 31:return Ze("Activity");default:return""}}function Ve(n){try{var a="",o=null;do a+=je(n,o),o=n,n=n.return;while(n);return a}catch(c){return` -Error generating stack: `+c.message+` -`+c.stack}}var $e=Object.prototype.hasOwnProperty,Nt=e.unstable_scheduleCallback,We=e.unstable_cancelCallback,it=e.unstable_shouldYield,Pt=e.unstable_requestPaint,Ie=e.unstable_now,Q=e.unstable_getCurrentPriorityLevel,Oe=e.unstable_ImmediatePriority,Xe=e.unstable_UserBlockingPriority,Lt=e.unstable_NormalPriority,ln=e.unstable_LowPriority,Kt=e.unstable_IdlePriority,gn=e.log,vn=e.unstable_setDisableYieldValue,yn=null,kt=null;function cn(n){if(typeof gn=="function"&&vn(n),kt&&typeof kt.setStrictMode=="function")try{kt.setStrictMode(yn,n)}catch{}}var Ot=Math.clz32?Math.clz32:le,Ur=Math.log,$r=Math.LN2;function le(n){return n>>>=0,n===0?32:31-(Ur(n)/$r|0)|0}var Me=256,at=262144,ot=4194304;function qt(n){var a=n&42;if(a!==0)return a;switch(n&-n){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 n&261888;case 262144:case 524288:case 1048576:case 2097152:return n&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return n&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return n}}function Pe(n,a,o){var c=n.pendingLanes;if(c===0)return 0;var p=0,v=n.suspendedLanes,w=n.pingedLanes;n=n.warmLanes;var N=c&134217727;return N!==0?(c=N&~v,c!==0?p=qt(c):(w&=N,w!==0?p=qt(w):o||(o=N&~n,o!==0&&(p=qt(o))))):(N=c&~v,N!==0?p=qt(N):w!==0?p=qt(w):o||(o=c&~n,o!==0&&(p=qt(o)))),p===0?0:a!==0&&a!==p&&(a&v)===0&&(v=p&-p,o=a&-a,v>=o||v===32&&(o&4194048)!==0)?a:p}function yt(n,a){return(n.pendingLanes&~(n.suspendedLanes&~n.pingedLanes)&a)===0}function Yt(n,a){switch(n){case 1:case 2:case 4:case 8:case 64:return a+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 a+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 un(){var n=ot;return ot<<=1,(ot&62914560)===0&&(ot=4194304),n}function xa(n){for(var a=[],o=0;31>o;o++)a.push(n);return a}function Ct(n,a){n.pendingLanes|=a,a!==268435456&&(n.suspendedLanes=0,n.pingedLanes=0,n.warmLanes=0)}function bn(n,a,o,c,p,v){var w=n.pendingLanes;n.pendingLanes=o,n.suspendedLanes=0,n.pingedLanes=0,n.warmLanes=0,n.expiredLanes&=o,n.entangledLanes&=o,n.errorRecoveryDisabledLanes&=o,n.shellSuspendCounter=0;var N=n.entanglements,M=n.expirationTimes,V=n.hiddenUpdates;for(o=w&~o;0"u")return null;try{return n.activeElement||n.body}catch{return n.body}}var $N=/[\n"\\]/g;function Kn(n){return n.replace($N,function(a){return"\\"+a.charCodeAt(0).toString(16)+" "})}function bf(n,a,o,c,p,v,w,N){n.name="",w!=null&&typeof w!="function"&&typeof w!="symbol"&&typeof w!="boolean"?n.type=w:n.removeAttribute("type"),a!=null?w==="number"?(a===0&&n.value===""||n.value!=a)&&(n.value=""+Fn(a)):n.value!==""+Fn(a)&&(n.value=""+Fn(a)):w!=="submit"&&w!=="reset"||n.removeAttribute("value"),a!=null?xf(n,w,Fn(a)):o!=null?xf(n,w,Fn(o)):c!=null&&n.removeAttribute("value"),p==null&&v!=null&&(n.defaultChecked=!!v),p!=null&&(n.checked=p&&typeof p!="function"&&typeof p!="symbol"),N!=null&&typeof N!="function"&&typeof N!="symbol"&&typeof N!="boolean"?n.name=""+Fn(N):n.removeAttribute("name")}function Av(n,a,o,c,p,v,w,N){if(v!=null&&typeof v!="function"&&typeof v!="symbol"&&typeof v!="boolean"&&(n.type=v),a!=null||o!=null){if(!(v!=="submit"&&v!=="reset"||a!=null)){yf(n);return}o=o!=null?""+Fn(o):"",a=a!=null?""+Fn(a):o,N||a===n.value||(n.value=a),n.defaultValue=a}c=c??p,c=typeof c!="function"&&typeof c!="symbol"&&!!c,n.checked=N?n.checked:!!c,n.defaultChecked=!!c,w!=null&&typeof w!="function"&&typeof w!="symbol"&&typeof w!="boolean"&&(n.name=w),yf(n)}function xf(n,a,o){a==="number"&&cc(n.ownerDocument)===n||n.defaultValue===""+o||(n.defaultValue=""+o)}function ei(n,a,o,c){if(n=n.options,a){a={};for(var p=0;p"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Cf=!1;if(qr)try{var xo={};Object.defineProperty(xo,"passive",{get:function(){Cf=!0}}),window.addEventListener("test",xo,xo),window.removeEventListener("test",xo,xo)}catch{Cf=!1}var _a=null,Tf=null,dc=null;function Pv(){if(dc)return dc;var n,a=Tf,o=a.length,c,p="value"in _a?_a.value:_a.textContent,v=p.length;for(n=0;n=So),Bv=" ",Hv=!1;function qv(n,a){switch(n){case"keyup":return mA.indexOf(a.keyCode)!==-1;case"keydown":return a.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Vv(n){return n=n.detail,typeof n=="object"&&"data"in n?n.data:null}var ai=!1;function gA(n,a){switch(n){case"compositionend":return Vv(a);case"keypress":return a.which!==32?null:(Hv=!0,Bv);case"textInput":return n=a.data,n===Bv&&Hv?null:n;default:return null}}function vA(n,a){if(ai)return n==="compositionend"||!kf&&qv(n,a)?(n=Pv(),dc=Tf=_a=null,ai=!1,n):null;switch(n){case"paste":return null;case"keypress":if(!(a.ctrlKey||a.altKey||a.metaKey)||a.ctrlKey&&a.altKey){if(a.char&&1=a)return{node:o,offset:a-n};n=c}e:{for(;o;){if(o.nextSibling){o=o.nextSibling;break e}o=o.parentNode}o=void 0}o=Qv(o)}}function ey(n,a){return n&&a?n===a?!0:n&&n.nodeType===3?!1:a&&a.nodeType===3?ey(n,a.parentNode):"contains"in n?n.contains(a):n.compareDocumentPosition?!!(n.compareDocumentPosition(a)&16):!1:!1}function ty(n){n=n!=null&&n.ownerDocument!=null&&n.ownerDocument.defaultView!=null?n.ownerDocument.defaultView:window;for(var a=cc(n.document);a instanceof n.HTMLIFrameElement;){try{var o=typeof a.contentWindow.location.href=="string"}catch{o=!1}if(o)n=a.contentWindow;else break;a=cc(n.document)}return a}function Df(n){var a=n&&n.nodeName&&n.nodeName.toLowerCase();return a&&(a==="input"&&(n.type==="text"||n.type==="search"||n.type==="tel"||n.type==="url"||n.type==="password")||a==="textarea"||n.contentEditable==="true")}var CA=qr&&"documentMode"in document&&11>=document.documentMode,si=null,Pf=null,No=null,Lf=!1;function ny(n,a,o){var c=o.window===o?o.document:o.nodeType===9?o:o.ownerDocument;Lf||si==null||si!==cc(c)||(c=si,"selectionStart"in c&&Df(c)?c={start:c.selectionStart,end:c.selectionEnd}:(c=(c.ownerDocument&&c.ownerDocument.defaultView||window).getSelection(),c={anchorNode:c.anchorNode,anchorOffset:c.anchorOffset,focusNode:c.focusNode,focusOffset:c.focusOffset}),No&&To(No,c)||(No=c,c=au(Pf,"onSelect"),0>=w,p-=w,_r=1<<32-Ot(a)+p|o<Ge?(tt=Ce,Ce=null):tt=Ce.sibling;var ct=F(z,Ce,q[Ge],ae);if(ct===null){Ce===null&&(Ce=tt);break}n&&Ce&&ct.alternate===null&&a(z,Ce),P=v(ct,P,Ge),lt===null?ke=ct:lt.sibling=ct,lt=ct,Ce=tt}if(Ge===q.length)return o(z,Ce),rt&&Gr(z,Ge),ke;if(Ce===null){for(;GeGe?(tt=Ce,Ce=null):tt=Ce.sibling;var Va=F(z,Ce,ct.value,ae);if(Va===null){Ce===null&&(Ce=tt);break}n&&Ce&&Va.alternate===null&&a(z,Ce),P=v(Va,P,Ge),lt===null?ke=Va:lt.sibling=Va,lt=Va,Ce=tt}if(ct.done)return o(z,Ce),rt&&Gr(z,Ge),ke;if(Ce===null){for(;!ct.done;Ge++,ct=q.next())ct=ie(z,ct.value,ae),ct!==null&&(P=v(ct,P,Ge),lt===null?ke=ct:lt.sibling=ct,lt=ct);return rt&&Gr(z,Ge),ke}for(Ce=c(Ce);!ct.done;Ge++,ct=q.next())ct=X(Ce,z,Ge,ct.value,ae),ct!==null&&(n&&ct.alternate!==null&&Ce.delete(ct.key===null?Ge:ct.key),P=v(ct,P,Ge),lt===null?ke=ct:lt.sibling=ct,lt=ct);return n&&Ce.forEach(function(GR){return a(z,GR)}),rt&&Gr(z,Ge),ke}function pt(z,P,q,ae){if(typeof q=="object"&&q!==null&&q.type===S&&q.key===null&&(q=q.props.children),typeof q=="object"&&q!==null){switch(q.$$typeof){case _:e:{for(var ke=q.key;P!==null;){if(P.key===ke){if(ke=q.type,ke===S){if(P.tag===7){o(z,P.sibling),ae=p(P,q.props.children),ae.return=z,z=ae;break e}}else if(P.elementType===ke||typeof ke=="object"&&ke!==null&&ke.$$typeof===H&&ws(ke)===P.type){o(z,P.sibling),ae=p(P,q.props),Mo(ae,q),ae.return=z,z=ae;break e}o(z,P);break}else a(z,P);P=P.sibling}q.type===S?(ae=gs(q.props.children,z.mode,ae,q.key),ae.return=z,z=ae):(ae=wc(q.type,q.key,q.props,null,z.mode,ae),Mo(ae,q),ae.return=z,z=ae)}return w(z);case E:e:{for(ke=q.key;P!==null;){if(P.key===ke)if(P.tag===4&&P.stateNode.containerInfo===q.containerInfo&&P.stateNode.implementation===q.implementation){o(z,P.sibling),ae=p(P,q.children||[]),ae.return=z,z=ae;break e}else{o(z,P);break}else a(z,P);P=P.sibling}ae=qf(q,z.mode,ae),ae.return=z,z=ae}return w(z);case H:return q=ws(q),pt(z,P,q,ae)}if(pe(q))return _e(z,P,q,ae);if(he(q)){if(ke=he(q),typeof ke!="function")throw Error(s(150));return q=ke.call(q),De(z,P,q,ae)}if(typeof q.then=="function")return pt(z,P,Ac(q),ae);if(q.$$typeof===A)return pt(z,P,Ec(z,q),ae);Rc(z,q)}return typeof q=="string"&&q!==""||typeof q=="number"||typeof q=="bigint"?(q=""+q,P!==null&&P.tag===6?(o(z,P.sibling),ae=p(P,q),ae.return=z,z=ae):(o(z,P),ae=Hf(q,z.mode,ae),ae.return=z,z=ae),w(z)):o(z,P)}return function(z,P,q,ae){try{Oo=0;var ke=pt(z,P,q,ae);return gi=null,ke}catch(Ce){if(Ce===pi||Ce===Tc)throw Ce;var lt=Ln(29,Ce,null,z.mode);return lt.lanes=ae,lt.return=z,lt}finally{}}}var Ss=Cy(!0),Ty=Cy(!1),Na=!1;function th(n){n.updateQueue={baseState:n.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function nh(n,a){n=n.updateQueue,a.updateQueue===n&&(a.updateQueue={baseState:n.baseState,firstBaseUpdate:n.firstBaseUpdate,lastBaseUpdate:n.lastBaseUpdate,shared:n.shared,callbacks:null})}function Aa(n){return{lane:n,tag:0,payload:null,callback:null,next:null}}function Ra(n,a,o){var c=n.updateQueue;if(c===null)return null;if(c=c.shared,(ut&2)!==0){var p=c.pending;return p===null?a.next=a:(a.next=p.next,p.next=a),c.pending=a,a=xc(n),cy(n,null,o),a}return bc(n,c,a,o),xc(n)}function Do(n,a,o){if(a=a.updateQueue,a!==null&&(a=a.shared,(o&4194048)!==0)){var c=a.lanes;c&=n.pendingLanes,o|=c,a.lanes=o,xn(n,o)}}function rh(n,a){var o=n.updateQueue,c=n.alternate;if(c!==null&&(c=c.updateQueue,o===c)){var p=null,v=null;if(o=o.firstBaseUpdate,o!==null){do{var w={lane:o.lane,tag:o.tag,payload:o.payload,callback:null,next:null};v===null?p=v=w:v=v.next=w,o=o.next}while(o!==null);v===null?p=v=a:v=v.next=a}else p=v=a;o={baseState:c.baseState,firstBaseUpdate:p,lastBaseUpdate:v,shared:c.shared,callbacks:c.callbacks},n.updateQueue=o;return}n=o.lastBaseUpdate,n===null?o.firstBaseUpdate=a:n.next=a,o.lastBaseUpdate=a}var ah=!1;function Po(){if(ah){var n=mi;if(n!==null)throw n}}function Lo(n,a,o,c){ah=!1;var p=n.updateQueue;Na=!1;var v=p.firstBaseUpdate,w=p.lastBaseUpdate,N=p.shared.pending;if(N!==null){p.shared.pending=null;var M=N,V=M.next;M.next=null,w===null?v=V:w.next=V,w=M;var ne=n.alternate;ne!==null&&(ne=ne.updateQueue,N=ne.lastBaseUpdate,N!==w&&(N===null?ne.firstBaseUpdate=V:N.next=V,ne.lastBaseUpdate=M))}if(v!==null){var ie=p.baseState;w=0,ne=V=M=null,N=v;do{var F=N.lane&-536870913,X=F!==N.lane;if(X?(et&F)===F:(c&F)===F){F!==0&&F===hi&&(ah=!0),ne!==null&&(ne=ne.next={lane:0,tag:N.tag,payload:N.payload,callback:null,next:null});e:{var _e=n,De=N;F=a;var pt=o;switch(De.tag){case 1:if(_e=De.payload,typeof _e=="function"){ie=_e.call(pt,ie,F);break e}ie=_e;break e;case 3:_e.flags=_e.flags&-65537|128;case 0:if(_e=De.payload,F=typeof _e=="function"?_e.call(pt,ie,F):_e,F==null)break e;ie=g({},ie,F);break e;case 2:Na=!0}}F=N.callback,F!==null&&(n.flags|=64,X&&(n.flags|=8192),X=p.callbacks,X===null?p.callbacks=[F]:X.push(F))}else X={lane:F,tag:N.tag,payload:N.payload,callback:N.callback,next:null},ne===null?(V=ne=X,M=ie):ne=ne.next=X,w|=F;if(N=N.next,N===null){if(N=p.shared.pending,N===null)break;X=N,N=X.next,X.next=null,p.lastBaseUpdate=X,p.shared.pending=null}}while(!0);ne===null&&(M=ie),p.baseState=M,p.firstBaseUpdate=V,p.lastBaseUpdate=ne,v===null&&(p.shared.lanes=0),Da|=w,n.lanes=w,n.memoizedState=ie}}function Ny(n,a){if(typeof n!="function")throw Error(s(191,n));n.call(a)}function Ay(n,a){var o=n.callbacks;if(o!==null)for(n.callbacks=null,n=0;nv?v:8;var w=j.T,N={};j.T=N,Sh(n,!1,a,o);try{var M=p(),V=j.S;if(V!==null&&V(N,M),M!==null&&typeof M=="object"&&typeof M.then=="function"){var ne=DA(M,c);Uo(n,a,ne,Bn(n))}else Uo(n,a,c,Bn(n))}catch(ie){Uo(n,a,{then:function(){},status:"rejected",reason:ie},Bn())}finally{K.p=v,w!==null&&N.types!==null&&(w.types=N.types),j.T=w}}function $A(){}function wh(n,a,o,c){if(n.tag!==5)throw Error(s(476));var p=ib(n).queue;sb(n,p,a,W,o===null?$A:function(){return ob(n),o(c)})}function ib(n){var a=n.memoizedState;if(a!==null)return a;a={memoizedState:W,baseState:W,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Wr,lastRenderedState:W},next:null};var o={};return a.next={memoizedState:o,baseState:o,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Wr,lastRenderedState:o},next:null},n.memoizedState=a,n=n.alternate,n!==null&&(n.memoizedState=a),a}function ob(n){var a=ib(n);a.next===null&&(a=n.alternate.memoizedState),Uo(n,a.next.queue,{},Bn())}function _h(){return tn(nl)}function lb(){return Dt().memoizedState}function cb(){return Dt().memoizedState}function BA(n){for(var a=n.return;a!==null;){switch(a.tag){case 24:case 3:var o=Bn();n=Aa(o);var c=Ra(a,n,o);c!==null&&(An(c,a,o),Do(c,a,o)),a={cache:Jf()},n.payload=a;return}a=a.return}}function HA(n,a,o){var c=Bn();o={lane:c,revertLane:0,gesture:null,action:o,hasEagerState:!1,eagerState:null,next:null},Uc(n)?db(a,o):(o=$f(n,a,o,c),o!==null&&(An(o,n,c),fb(o,a,c)))}function ub(n,a,o){var c=Bn();Uo(n,a,o,c)}function Uo(n,a,o,c){var p={lane:c,revertLane:0,gesture:null,action:o,hasEagerState:!1,eagerState:null,next:null};if(Uc(n))db(a,p);else{var v=n.alternate;if(n.lanes===0&&(v===null||v.lanes===0)&&(v=a.lastRenderedReducer,v!==null))try{var w=a.lastRenderedState,N=v(w,o);if(p.hasEagerState=!0,p.eagerState=N,Pn(N,w))return bc(n,a,p,0),vt===null&&yc(),!1}catch{}finally{}if(o=$f(n,a,p,c),o!==null)return An(o,n,c),fb(o,a,c),!0}return!1}function Sh(n,a,o,c){if(c={lane:2,revertLane:tm(),gesture:null,action:c,hasEagerState:!1,eagerState:null,next:null},Uc(n)){if(a)throw Error(s(479))}else a=$f(n,o,c,2),a!==null&&An(a,n,2)}function Uc(n){var a=n.alternate;return n===Be||a!==null&&a===Be}function db(n,a){yi=Oc=!0;var o=n.pending;o===null?a.next=a:(a.next=o.next,o.next=a),n.pending=a}function fb(n,a,o){if((o&4194048)!==0){var c=a.lanes;c&=n.pendingLanes,o|=c,a.lanes=o,xn(n,o)}}var $o={readContext:tn,use:Pc,useCallback:At,useContext:At,useEffect:At,useImperativeHandle:At,useLayoutEffect:At,useInsertionEffect:At,useMemo:At,useReducer:At,useRef:At,useState:At,useDebugValue:At,useDeferredValue:At,useTransition:At,useSyncExternalStore:At,useId:At,useHostTransitionStatus:At,useFormState:At,useActionState:At,useOptimistic:At,useMemoCache:At,useCacheRefresh:At};$o.useEffectEvent=At;var hb={readContext:tn,use:Pc,useCallback:function(n,a){return hn().memoizedState=[n,a===void 0?null:a],n},useContext:tn,useEffect:Xy,useImperativeHandle:function(n,a,o){o=o!=null?o.concat([n]):null,zc(4194308,4,eb.bind(null,a,n),o)},useLayoutEffect:function(n,a){return zc(4194308,4,n,a)},useInsertionEffect:function(n,a){zc(4,2,n,a)},useMemo:function(n,a){var o=hn();a=a===void 0?null:a;var c=n();if(Es){cn(!0);try{n()}finally{cn(!1)}}return o.memoizedState=[c,a],c},useReducer:function(n,a,o){var c=hn();if(o!==void 0){var p=o(a);if(Es){cn(!0);try{o(a)}finally{cn(!1)}}}else p=a;return c.memoizedState=c.baseState=p,n={pending:null,lanes:0,dispatch:null,lastRenderedReducer:n,lastRenderedState:p},c.queue=n,n=n.dispatch=HA.bind(null,Be,n),[c.memoizedState,n]},useRef:function(n){var a=hn();return n={current:n},a.memoizedState=n},useState:function(n){n=gh(n);var a=n.queue,o=ub.bind(null,Be,a);return a.dispatch=o,[n.memoizedState,o]},useDebugValue:bh,useDeferredValue:function(n,a){var o=hn();return xh(o,n,a)},useTransition:function(){var n=gh(!1);return n=sb.bind(null,Be,n.queue,!0,!1),hn().memoizedState=n,[!1,n]},useSyncExternalStore:function(n,a,o){var c=Be,p=hn();if(rt){if(o===void 0)throw Error(s(407));o=o()}else{if(o=a(),vt===null)throw Error(s(349));(et&127)!==0||Dy(c,a,o)}p.memoizedState=o;var v={value:o,getSnapshot:a};return p.queue=v,Xy(Ly.bind(null,c,v,n),[n]),c.flags|=2048,xi(9,{destroy:void 0},Py.bind(null,c,v,o,a),null),o},useId:function(){var n=hn(),a=vt.identifierPrefix;if(rt){var o=Sr,c=_r;o=(c&~(1<<32-Ot(c)-1)).toString(32)+o,a="_"+a+"R_"+o,o=Mc++,0<\/script>",v=v.removeChild(v.firstChild);break;case"select":v=typeof c.is=="string"?w.createElement("select",{is:c.is}):w.createElement("select"),c.multiple?v.multiple=!0:c.size&&(v.size=c.size);break;default:v=typeof c.is=="string"?w.createElement(p,{is:c.is}):w.createElement(p)}}v[Zt]=a,v[_n]=c;e:for(w=a.child;w!==null;){if(w.tag===5||w.tag===6)v.appendChild(w.stateNode);else if(w.tag!==4&&w.tag!==27&&w.child!==null){w.child.return=w,w=w.child;continue}if(w===a)break e;for(;w.sibling===null;){if(w.return===null||w.return===a)break e;w=w.return}w.sibling.return=w.return,w=w.sibling}a.stateNode=v;e:switch(rn(v,p,c),p){case"button":case"input":case"select":case"textarea":c=!!c.autoFocus;break e;case"img":c=!0;break e;default:c=!1}c&&Jr(a)}}return _t(a),zh(a,a.type,n===null?null:n.memoizedProps,a.pendingProps,o),null;case 6:if(n&&a.stateNode!=null)n.memoizedProps!==c&&Jr(a);else{if(typeof c!="string"&&a.stateNode===null)throw Error(s(166));if(n=re.current,di(a)){if(n=a.stateNode,o=a.memoizedProps,c=null,p=en,p!==null)switch(p.tag){case 27:case 5:c=p.memoizedProps}n[Zt]=a,n=!!(n.nodeValue===o||c!==null&&c.suppressHydrationWarning===!0||O0(n.nodeValue,o)),n||Ca(a,!0)}else n=su(n).createTextNode(c),n[Zt]=a,a.stateNode=n}return _t(a),null;case 31:if(o=a.memoizedState,n===null||n.memoizedState!==null){if(c=di(a),o!==null){if(n===null){if(!c)throw Error(s(318));if(n=a.memoizedState,n=n!==null?n.dehydrated:null,!n)throw Error(s(557));n[Zt]=a}else vs(),(a.flags&128)===0&&(a.memoizedState=null),a.flags|=4;_t(a),n=!1}else o=Kf(),n!==null&&n.memoizedState!==null&&(n.memoizedState.hydrationErrors=o),n=!0;if(!n)return a.flags&256?(In(a),a):(In(a),null);if((a.flags&128)!==0)throw Error(s(558))}return _t(a),null;case 13:if(c=a.memoizedState,n===null||n.memoizedState!==null&&n.memoizedState.dehydrated!==null){if(p=di(a),c!==null&&c.dehydrated!==null){if(n===null){if(!p)throw Error(s(318));if(p=a.memoizedState,p=p!==null?p.dehydrated:null,!p)throw Error(s(317));p[Zt]=a}else vs(),(a.flags&128)===0&&(a.memoizedState=null),a.flags|=4;_t(a),p=!1}else p=Kf(),n!==null&&n.memoizedState!==null&&(n.memoizedState.hydrationErrors=p),p=!0;if(!p)return a.flags&256?(In(a),a):(In(a),null)}return In(a),(a.flags&128)!==0?(a.lanes=o,a):(o=c!==null,n=n!==null&&n.memoizedState!==null,o&&(c=a.child,p=null,c.alternate!==null&&c.alternate.memoizedState!==null&&c.alternate.memoizedState.cachePool!==null&&(p=c.alternate.memoizedState.cachePool.pool),v=null,c.memoizedState!==null&&c.memoizedState.cachePool!==null&&(v=c.memoizedState.cachePool.pool),v!==p&&(c.flags|=2048)),o!==n&&o&&(a.child.flags|=8192),Vc(a,a.updateQueue),_t(a),null);case 4:return se(),n===null&&sm(a.stateNode.containerInfo),_t(a),null;case 10:return Kr(a.type),_t(a),null;case 19:if(O(Mt),c=a.memoizedState,c===null)return _t(a),null;if(p=(a.flags&128)!==0,v=c.rendering,v===null)if(p)Ho(c,!1);else{if(Rt!==0||n!==null&&(n.flags&128)!==0)for(n=a.child;n!==null;){if(v=kc(n),v!==null){for(a.flags|=128,Ho(c,!1),n=v.updateQueue,a.updateQueue=n,Vc(a,n),a.subtreeFlags=0,n=o,o=a.child;o!==null;)uy(o,n),o=o.sibling;return U(Mt,Mt.current&1|2),rt&&Gr(a,c.treeForkCount),a.child}n=n.sibling}c.tail!==null&&Ie()>Wc&&(a.flags|=128,p=!0,Ho(c,!1),a.lanes=4194304)}else{if(!p)if(n=kc(v),n!==null){if(a.flags|=128,p=!0,n=n.updateQueue,a.updateQueue=n,Vc(a,n),Ho(c,!0),c.tail===null&&c.tailMode==="hidden"&&!v.alternate&&!rt)return _t(a),null}else 2*Ie()-c.renderingStartTime>Wc&&o!==536870912&&(a.flags|=128,p=!0,Ho(c,!1),a.lanes=4194304);c.isBackwards?(v.sibling=a.child,a.child=v):(n=c.last,n!==null?n.sibling=v:a.child=v,c.last=v)}return c.tail!==null?(n=c.tail,c.rendering=n,c.tail=n.sibling,c.renderingStartTime=Ie(),n.sibling=null,o=Mt.current,U(Mt,p?o&1|2:o&1),rt&&Gr(a,c.treeForkCount),n):(_t(a),null);case 22:case 23:return In(a),ih(),c=a.memoizedState!==null,n!==null?n.memoizedState!==null!==c&&(a.flags|=8192):c&&(a.flags|=8192),c?(o&536870912)!==0&&(a.flags&128)===0&&(_t(a),a.subtreeFlags&6&&(a.flags|=8192)):_t(a),o=a.updateQueue,o!==null&&Vc(a,o.retryQueue),o=null,n!==null&&n.memoizedState!==null&&n.memoizedState.cachePool!==null&&(o=n.memoizedState.cachePool.pool),c=null,a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(c=a.memoizedState.cachePool.pool),c!==o&&(a.flags|=2048),n!==null&&O(xs),null;case 24:return o=null,n!==null&&(o=n.memoizedState.cache),a.memoizedState.cache!==o&&(a.flags|=2048),Kr(zt),_t(a),null;case 25:return null;case 30:return null}throw Error(s(156,a.tag))}function KA(n,a){switch(Gf(a),a.tag){case 1:return n=a.flags,n&65536?(a.flags=n&-65537|128,a):null;case 3:return Kr(zt),se(),n=a.flags,(n&65536)!==0&&(n&128)===0?(a.flags=n&-65537|128,a):null;case 26:case 27:case 5:return Te(a),null;case 31:if(a.memoizedState!==null){if(In(a),a.alternate===null)throw Error(s(340));vs()}return n=a.flags,n&65536?(a.flags=n&-65537|128,a):null;case 13:if(In(a),n=a.memoizedState,n!==null&&n.dehydrated!==null){if(a.alternate===null)throw Error(s(340));vs()}return n=a.flags,n&65536?(a.flags=n&-65537|128,a):null;case 19:return O(Mt),null;case 4:return se(),null;case 10:return Kr(a.type),null;case 22:case 23:return In(a),ih(),n!==null&&O(xs),n=a.flags,n&65536?(a.flags=n&-65537|128,a):null;case 24:return Kr(zt),null;case 25:return null;default:return null}}function zb(n,a){switch(Gf(a),a.tag){case 3:Kr(zt),se();break;case 26:case 27:case 5:Te(a);break;case 4:se();break;case 31:a.memoizedState!==null&&In(a);break;case 13:In(a);break;case 19:O(Mt);break;case 10:Kr(a.type);break;case 22:case 23:In(a),ih(),n!==null&&O(xs);break;case 24:Kr(zt)}}function qo(n,a){try{var o=a.updateQueue,c=o!==null?o.lastEffect:null;if(c!==null){var p=c.next;o=p;do{if((o.tag&n)===n){c=void 0;var v=o.create,w=o.inst;c=v(),w.destroy=c}o=o.next}while(o!==p)}}catch(N){ft(a,a.return,N)}}function Oa(n,a,o){try{var c=a.updateQueue,p=c!==null?c.lastEffect:null;if(p!==null){var v=p.next;c=v;do{if((c.tag&n)===n){var w=c.inst,N=w.destroy;if(N!==void 0){w.destroy=void 0,p=a;var M=o,V=N;try{V()}catch(ne){ft(p,M,ne)}}}c=c.next}while(c!==v)}}catch(ne){ft(a,a.return,ne)}}function Ib(n){var a=n.updateQueue;if(a!==null){var o=n.stateNode;try{Ay(a,o)}catch(c){ft(n,n.return,c)}}}function Ub(n,a,o){o.props=Cs(n.type,n.memoizedProps),o.state=n.memoizedState;try{o.componentWillUnmount()}catch(c){ft(n,a,c)}}function Vo(n,a){try{var o=n.ref;if(o!==null){switch(n.tag){case 26:case 27:case 5:var c=n.stateNode;break;case 30:c=n.stateNode;break;default:c=n.stateNode}typeof o=="function"?n.refCleanup=o(c):o.current=c}}catch(p){ft(n,a,p)}}function Er(n,a){var o=n.ref,c=n.refCleanup;if(o!==null)if(typeof c=="function")try{c()}catch(p){ft(n,a,p)}finally{n.refCleanup=null,n=n.alternate,n!=null&&(n.refCleanup=null)}else if(typeof o=="function")try{o(null)}catch(p){ft(n,a,p)}else o.current=null}function $b(n){var a=n.type,o=n.memoizedProps,c=n.stateNode;try{e:switch(a){case"button":case"input":case"select":case"textarea":o.autoFocus&&c.focus();break e;case"img":o.src?c.src=o.src:o.srcSet&&(c.srcset=o.srcSet)}}catch(p){ft(n,n.return,p)}}function Ih(n,a,o){try{var c=n.stateNode;pR(c,n.type,o,a),c[_n]=a}catch(p){ft(n,n.return,p)}}function Bb(n){return n.tag===5||n.tag===3||n.tag===26||n.tag===27&&Ua(n.type)||n.tag===4}function Uh(n){e:for(;;){for(;n.sibling===null;){if(n.return===null||Bb(n.return))return null;n=n.return}for(n.sibling.return=n.return,n=n.sibling;n.tag!==5&&n.tag!==6&&n.tag!==18;){if(n.tag===27&&Ua(n.type)||n.flags&2||n.child===null||n.tag===4)continue e;n.child.return=n,n=n.child}if(!(n.flags&2))return n.stateNode}}function $h(n,a,o){var c=n.tag;if(c===5||c===6)n=n.stateNode,a?(o.nodeType===9?o.body:o.nodeName==="HTML"?o.ownerDocument.body:o).insertBefore(n,a):(a=o.nodeType===9?o.body:o.nodeName==="HTML"?o.ownerDocument.body:o,a.appendChild(n),o=o._reactRootContainer,o!=null||a.onclick!==null||(a.onclick=Hr));else if(c!==4&&(c===27&&Ua(n.type)&&(o=n.stateNode,a=null),n=n.child,n!==null))for($h(n,a,o),n=n.sibling;n!==null;)$h(n,a,o),n=n.sibling}function Gc(n,a,o){var c=n.tag;if(c===5||c===6)n=n.stateNode,a?o.insertBefore(n,a):o.appendChild(n);else if(c!==4&&(c===27&&Ua(n.type)&&(o=n.stateNode),n=n.child,n!==null))for(Gc(n,a,o),n=n.sibling;n!==null;)Gc(n,a,o),n=n.sibling}function Hb(n){var a=n.stateNode,o=n.memoizedProps;try{for(var c=n.type,p=a.attributes;p.length;)a.removeAttributeNode(p[0]);rn(a,c,o),a[Zt]=n,a[_n]=o}catch(v){ft(n,n.return,v)}}var Qr=!1,$t=!1,Bh=!1,qb=typeof WeakSet=="function"?WeakSet:Set,Xt=null;function YA(n,a){if(n=n.containerInfo,lm=fu,n=ty(n),Df(n)){if("selectionStart"in n)var o={start:n.selectionStart,end:n.selectionEnd};else e:{o=(o=n.ownerDocument)&&o.defaultView||window;var c=o.getSelection&&o.getSelection();if(c&&c.rangeCount!==0){o=c.anchorNode;var p=c.anchorOffset,v=c.focusNode;c=c.focusOffset;try{o.nodeType,v.nodeType}catch{o=null;break e}var w=0,N=-1,M=-1,V=0,ne=0,ie=n,F=null;t:for(;;){for(var X;ie!==o||p!==0&&ie.nodeType!==3||(N=w+p),ie!==v||c!==0&&ie.nodeType!==3||(M=w+c),ie.nodeType===3&&(w+=ie.nodeValue.length),(X=ie.firstChild)!==null;)F=ie,ie=X;for(;;){if(ie===n)break t;if(F===o&&++V===p&&(N=w),F===v&&++ne===c&&(M=w),(X=ie.nextSibling)!==null)break;ie=F,F=ie.parentNode}ie=X}o=N===-1||M===-1?null:{start:N,end:M}}else o=null}o=o||{start:0,end:0}}else o=null;for(cm={focusedElem:n,selectionRange:o},fu=!1,Xt=a;Xt!==null;)if(a=Xt,n=a.child,(a.subtreeFlags&1028)!==0&&n!==null)n.return=a,Xt=n;else for(;Xt!==null;){switch(a=Xt,v=a.alternate,n=a.flags,a.tag){case 0:if((n&4)!==0&&(n=a.updateQueue,n=n!==null?n.events:null,n!==null))for(o=0;o title"))),rn(v,c,o),v[Zt]=n,Wt(v),c=v;break e;case"link":var w=W0("link","href",p).get(c+(o.href||""));if(w){for(var N=0;Npt&&(w=pt,pt=De,De=w);var z=Zv(N,De),P=Zv(N,pt);if(z&&P&&(X.rangeCount!==1||X.anchorNode!==z.node||X.anchorOffset!==z.offset||X.focusNode!==P.node||X.focusOffset!==P.offset)){var q=ie.createRange();q.setStart(z.node,z.offset),X.removeAllRanges(),De>pt?(X.addRange(q),X.extend(P.node,P.offset)):(q.setEnd(P.node,P.offset),X.addRange(q))}}}}for(ie=[],X=N;X=X.parentNode;)X.nodeType===1&&ie.push({element:X,left:X.scrollLeft,top:X.scrollTop});for(typeof N.focus=="function"&&N.focus(),N=0;No?32:o,j.T=null,o=Yh,Yh=null;var v=La,w=ra;if(Vt=0,Ci=La=null,ra=0,(ut&6)!==0)throw Error(s(331));var N=ut;if(ut|=4,e0(v.current),Jb(v,v.current,w,o),ut=N,Xo(0,!1),kt&&typeof kt.onPostCommitFiberRoot=="function")try{kt.onPostCommitFiberRoot(yn,v)}catch{}return!0}finally{K.p=p,j.T=c,y0(n,a)}}function x0(n,a,o){a=Wn(o,a),a=Nh(n.stateNode,a,2),n=Ra(n,a,2),n!==null&&(Ct(n,2),Cr(n))}function ft(n,a,o){if(n.tag===3)x0(n,n,o);else for(;a!==null;){if(a.tag===3){x0(a,n,o);break}else if(a.tag===1){var c=a.stateNode;if(typeof a.type.getDerivedStateFromError=="function"||typeof c.componentDidCatch=="function"&&(Pa===null||!Pa.has(c))){n=Wn(o,n),o=wb(2),c=Ra(a,o,2),c!==null&&(_b(o,c,a,n),Ct(c,2),Cr(c));break}}a=a.return}}function Qh(n,a,o){var c=n.pingCache;if(c===null){c=n.pingCache=new JA;var p=new Set;c.set(a,p)}else p=c.get(a),p===void 0&&(p=new Set,c.set(a,p));p.has(o)||(Vh=!0,p.add(o),n=nR.bind(null,n,a,o),a.then(n,n))}function nR(n,a,o){var c=n.pingCache;c!==null&&c.delete(a),n.pingedLanes|=n.suspendedLanes&o,n.warmLanes&=~o,vt===n&&(et&o)===o&&(Rt===4||Rt===3&&(et&62914560)===et&&300>Ie()-Yc?(ut&2)===0&&Ti(n,0):Gh|=o,Ei===et&&(Ei=0)),Cr(n)}function w0(n,a){a===0&&(a=un()),n=ps(n,a),n!==null&&(Ct(n,a),Cr(n))}function rR(n){var a=n.memoizedState,o=0;a!==null&&(o=a.retryLane),w0(n,o)}function aR(n,a){var o=0;switch(n.tag){case 31:case 13:var c=n.stateNode,p=n.memoizedState;p!==null&&(o=p.retryLane);break;case 19:c=n.stateNode;break;case 22:c=n.stateNode._retryCache;break;default:throw Error(s(314))}c!==null&&c.delete(a),w0(n,o)}function sR(n,a){return Nt(n,a)}var tu=null,Ai=null,Zh=!1,nu=!1,em=!1,Ia=0;function Cr(n){n!==Ai&&n.next===null&&(Ai===null?tu=Ai=n:Ai=Ai.next=n),nu=!0,Zh||(Zh=!0,oR())}function Xo(n,a){if(!em&&nu){em=!0;do for(var o=!1,c=tu;c!==null;){if(n!==0){var p=c.pendingLanes;if(p===0)var v=0;else{var w=c.suspendedLanes,N=c.pingedLanes;v=(1<<31-Ot(42|n)+1)-1,v&=p&~(w&~N),v=v&201326741?v&201326741|1:v?v|2:0}v!==0&&(o=!0,C0(c,v))}else v=et,v=Pe(c,c===vt?v:0,c.cancelPendingCommit!==null||c.timeoutHandle!==-1),(v&3)===0||yt(c,v)||(o=!0,C0(c,v));c=c.next}while(o);em=!1}}function iR(){_0()}function _0(){nu=Zh=!1;var n=0;Ia!==0&&vR()&&(n=Ia);for(var a=Ie(),o=null,c=tu;c!==null;){var p=c.next,v=S0(c,a);v===0?(c.next=null,o===null?tu=p:o.next=p,p===null&&(Ai=o)):(o=c,(n!==0||(v&3)!==0)&&(nu=!0)),c=p}Vt!==0&&Vt!==5||Xo(n),Ia!==0&&(Ia=0)}function S0(n,a){for(var o=n.suspendedLanes,c=n.pingedLanes,p=n.expirationTimes,v=n.pendingLanes&-62914561;0N)break;var ne=M.transferSize,ie=M.initiatorType;ne&&M0(ie)&&(M=M.responseEnd,w+=ne*(M"u"?null:document;function G0(n,a,o){var c=Ri;if(c&&typeof a=="string"&&a){var p=Kn(a);p='link[rel="'+n+'"][href="'+p+'"]',typeof o=="string"&&(p+='[crossorigin="'+o+'"]'),V0.has(p)||(V0.add(p),n={rel:n,crossOrigin:o,href:a},c.querySelector(p)===null&&(a=c.createElement("link"),rn(a,"link",n),Wt(a),c.head.appendChild(a)))}}function TR(n){aa.D(n),G0("dns-prefetch",n,null)}function NR(n,a){aa.C(n,a),G0("preconnect",n,a)}function AR(n,a,o){aa.L(n,a,o);var c=Ri;if(c&&n&&a){var p='link[rel="preload"][as="'+Kn(a)+'"]';a==="image"&&o&&o.imageSrcSet?(p+='[imagesrcset="'+Kn(o.imageSrcSet)+'"]',typeof o.imageSizes=="string"&&(p+='[imagesizes="'+Kn(o.imageSizes)+'"]')):p+='[href="'+Kn(n)+'"]';var v=p;switch(a){case"style":v=ji(n);break;case"script":v=ki(n)}tr.has(v)||(n=g({rel:"preload",href:a==="image"&&o&&o.imageSrcSet?void 0:n,as:a},o),tr.set(v,n),c.querySelector(p)!==null||a==="style"&&c.querySelector(el(v))||a==="script"&&c.querySelector(tl(v))||(a=c.createElement("link"),rn(a,"link",n),Wt(a),c.head.appendChild(a)))}}function RR(n,a){aa.m(n,a);var o=Ri;if(o&&n){var c=a&&typeof a.as=="string"?a.as:"script",p='link[rel="modulepreload"][as="'+Kn(c)+'"][href="'+Kn(n)+'"]',v=p;switch(c){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":v=ki(n)}if(!tr.has(v)&&(n=g({rel:"modulepreload",href:n},a),tr.set(v,n),o.querySelector(p)===null)){switch(c){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(o.querySelector(tl(v)))return}c=o.createElement("link"),rn(c,"link",n),Wt(c),o.head.appendChild(c)}}}function jR(n,a,o){aa.S(n,a,o);var c=Ri;if(c&&n){var p=Qs(c).hoistableStyles,v=ji(n);a=a||"default";var w=p.get(v);if(!w){var N={loading:0,preload:null};if(w=c.querySelector(el(v)))N.loading=5;else{n=g({rel:"stylesheet",href:n,"data-precedence":a},o),(o=tr.get(v))&&gm(n,o);var M=w=c.createElement("link");Wt(M),rn(M,"link",n),M._p=new Promise(function(V,ne){M.onload=V,M.onerror=ne}),M.addEventListener("load",function(){N.loading|=1}),M.addEventListener("error",function(){N.loading|=2}),N.loading|=4,ou(w,a,c)}w={type:"stylesheet",instance:w,count:1,state:N},p.set(v,w)}}}function kR(n,a){aa.X(n,a);var o=Ri;if(o&&n){var c=Qs(o).hoistableScripts,p=ki(n),v=c.get(p);v||(v=o.querySelector(tl(p)),v||(n=g({src:n,async:!0},a),(a=tr.get(p))&&vm(n,a),v=o.createElement("script"),Wt(v),rn(v,"link",n),o.head.appendChild(v)),v={type:"script",instance:v,count:1,state:null},c.set(p,v))}}function OR(n,a){aa.M(n,a);var o=Ri;if(o&&n){var c=Qs(o).hoistableScripts,p=ki(n),v=c.get(p);v||(v=o.querySelector(tl(p)),v||(n=g({src:n,async:!0,type:"module"},a),(a=tr.get(p))&&vm(n,a),v=o.createElement("script"),Wt(v),rn(v,"link",n),o.head.appendChild(v)),v={type:"script",instance:v,count:1,state:null},c.set(p,v))}}function F0(n,a,o,c){var p=(p=re.current)?iu(p):null;if(!p)throw Error(s(446));switch(n){case"meta":case"title":return null;case"style":return typeof o.precedence=="string"&&typeof o.href=="string"?(a=ji(o.href),o=Qs(p).hoistableStyles,c=o.get(a),c||(c={type:"style",instance:null,count:0,state:null},o.set(a,c)),c):{type:"void",instance:null,count:0,state:null};case"link":if(o.rel==="stylesheet"&&typeof o.href=="string"&&typeof o.precedence=="string"){n=ji(o.href);var v=Qs(p).hoistableStyles,w=v.get(n);if(w||(p=p.ownerDocument||p,w={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},v.set(n,w),(v=p.querySelector(el(n)))&&!v._p&&(w.instance=v,w.state.loading=5),tr.has(n)||(o={rel:"preload",as:"style",href:o.href,crossOrigin:o.crossOrigin,integrity:o.integrity,media:o.media,hrefLang:o.hrefLang,referrerPolicy:o.referrerPolicy},tr.set(n,o),v||MR(p,n,o,w.state))),a&&c===null)throw Error(s(528,""));return w}if(a&&c!==null)throw Error(s(529,""));return null;case"script":return a=o.async,o=o.src,typeof o=="string"&&a&&typeof a!="function"&&typeof a!="symbol"?(a=ki(o),o=Qs(p).hoistableScripts,c=o.get(a),c||(c={type:"script",instance:null,count:0,state:null},o.set(a,c)),c):{type:"void",instance:null,count:0,state:null};default:throw Error(s(444,n))}}function ji(n){return'href="'+Kn(n)+'"'}function el(n){return'link[rel="stylesheet"]['+n+"]"}function K0(n){return g({},n,{"data-precedence":n.precedence,precedence:null})}function MR(n,a,o,c){n.querySelector('link[rel="preload"][as="style"]['+a+"]")?c.loading=1:(a=n.createElement("link"),c.preload=a,a.addEventListener("load",function(){return c.loading|=1}),a.addEventListener("error",function(){return c.loading|=2}),rn(a,"link",o),Wt(a),n.head.appendChild(a))}function ki(n){return'[src="'+Kn(n)+'"]'}function tl(n){return"script[async]"+n}function Y0(n,a,o){if(a.count++,a.instance===null)switch(a.type){case"style":var c=n.querySelector('style[data-href~="'+Kn(o.href)+'"]');if(c)return a.instance=c,Wt(c),c;var p=g({},o,{"data-href":o.href,"data-precedence":o.precedence,href:null,precedence:null});return c=(n.ownerDocument||n).createElement("style"),Wt(c),rn(c,"style",p),ou(c,o.precedence,n),a.instance=c;case"stylesheet":p=ji(o.href);var v=n.querySelector(el(p));if(v)return a.state.loading|=4,a.instance=v,Wt(v),v;c=K0(o),(p=tr.get(p))&&gm(c,p),v=(n.ownerDocument||n).createElement("link"),Wt(v);var w=v;return w._p=new Promise(function(N,M){w.onload=N,w.onerror=M}),rn(v,"link",c),a.state.loading|=4,ou(v,o.precedence,n),a.instance=v;case"script":return v=ki(o.src),(p=n.querySelector(tl(v)))?(a.instance=p,Wt(p),p):(c=o,(p=tr.get(v))&&(c=g({},o),vm(c,p)),n=n.ownerDocument||n,p=n.createElement("script"),Wt(p),rn(p,"link",c),n.head.appendChild(p),a.instance=p);case"void":return null;default:throw Error(s(443,a.type))}else a.type==="stylesheet"&&(a.state.loading&4)===0&&(c=a.instance,a.state.loading|=4,ou(c,o.precedence,n));return a.instance}function ou(n,a,o){for(var c=o.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),p=c.length?c[c.length-1]:null,v=p,w=0;w title"):null)}function DR(n,a,o){if(o===1||a.itemProp!=null)return!1;switch(n){case"meta":case"title":return!0;case"style":if(typeof a.precedence!="string"||typeof a.href!="string"||a.href==="")break;return!0;case"link":if(typeof a.rel!="string"||typeof a.href!="string"||a.href===""||a.onLoad||a.onError)break;switch(a.rel){case"stylesheet":return n=a.disabled,typeof a.precedence=="string"&&n==null;default:return!0}case"script":if(a.async&&typeof a.async!="function"&&typeof a.async!="symbol"&&!a.onLoad&&!a.onError&&a.src&&typeof a.src=="string")return!0}return!1}function J0(n){return!(n.type==="stylesheet"&&(n.state.loading&3)===0)}function PR(n,a,o,c){if(o.type==="stylesheet"&&(typeof c.media!="string"||matchMedia(c.media).matches!==!1)&&(o.state.loading&4)===0){if(o.instance===null){var p=ji(c.href),v=a.querySelector(el(p));if(v){a=v._p,a!==null&&typeof a=="object"&&typeof a.then=="function"&&(n.count++,n=cu.bind(n),a.then(n,n)),o.state.loading|=4,o.instance=v,Wt(v);return}v=a.ownerDocument||a,c=K0(c),(p=tr.get(p))&&gm(c,p),v=v.createElement("link"),Wt(v);var w=v;w._p=new Promise(function(N,M){w.onload=N,w.onerror=M}),rn(v,"link",c),o.instance=v}n.stylesheets===null&&(n.stylesheets=new Map),n.stylesheets.set(o,a),(a=o.state.preload)&&(o.state.loading&3)===0&&(n.count++,o=cu.bind(n),a.addEventListener("load",o),a.addEventListener("error",o))}}var ym=0;function LR(n,a){return n.stylesheets&&n.count===0&&du(n,n.stylesheets),0ym?50:800)+a);return n.unsuspend=o,function(){n.unsuspend=null,clearTimeout(c),clearTimeout(p)}}:null}function cu(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)du(this,this.stylesheets);else if(this.unsuspend){var n=this.unsuspend;this.unsuspend=null,n()}}}var uu=null;function du(n,a){n.stylesheets=null,n.unsuspend!==null&&(n.count++,uu=new Map,a.forEach(zR,n),uu=null,cu.call(n))}function zR(n,a){if(!(a.state.loading&4)){var o=uu.get(n);if(o)var c=o.get(null);else{o=new Map,uu.set(n,o);for(var p=n.querySelectorAll("link[data-precedence],style[data-precedence]"),v=0;v"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(t){console.error(t)}}return e(),Nm.exports=ZR(),Nm.exports}var tj=ej();/** - * react-router v7.15.1 - * - * Copyright (c) Remix Software Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE.md file in the root directory of this source tree. - * - * @license MIT - */var wx="popstate";function _x(e){return typeof e=="object"&&e!=null&&"pathname"in e&&"search"in e&&"hash"in e&&"state"in e&&"key"in e}function nj(e={}){function t(s,i){var h;let l=(h=i.state)==null?void 0:h.masked,{pathname:u,search:d,hash:f}=l||s.location;return fp("",{pathname:u,search:d,hash:f},i.state&&i.state.usr||null,i.state&&i.state.key||"default",l?{pathname:s.location.pathname,search:s.location.search,hash:s.location.hash}:void 0)}function r(s,i){return typeof i=="string"?i:Cl(i)}return aj(t,r,null,e)}function Tt(e,t){if(e===!1||e===null||typeof e>"u")throw new Error(t)}function Lr(e,t){if(!e){typeof console<"u"&&console.warn(t);try{throw new Error(t)}catch{}}}function rj(){return Math.random().toString(36).substring(2,10)}function Sx(e,t){return{usr:e.state,key:e.key,idx:t,masked:e.mask?{pathname:e.pathname,search:e.search,hash:e.hash}:void 0}}function fp(e,t,r=null,s,i){return{pathname:typeof e=="string"?e:e.pathname,search:"",hash:"",...typeof t=="string"?lo(t):t,state:r,key:t&&t.key||s||rj(),mask:i}}function Cl({pathname:e="/",search:t="",hash:r=""}){return t&&t!=="?"&&(e+=t.charAt(0)==="?"?t:"?"+t),r&&r!=="#"&&(e+=r.charAt(0)==="#"?r:"#"+r),e}function lo(e){let t={};if(e){let r=e.indexOf("#");r>=0&&(t.hash=e.substring(r),e=e.substring(0,r));let s=e.indexOf("?");s>=0&&(t.search=e.substring(s),e=e.substring(0,s)),e&&(t.pathname=e)}return t}function aj(e,t,r,s={}){let{window:i=document.defaultView,v5Compat:l=!1}=s,u=i.history,d="POP",f=null,h=b();h==null&&(h=0,u.replaceState({...u.state,idx:h},""));function b(){return(u.state||{idx:null}).idx}function g(){d="POP";let T=b(),C=T==null?null:T-h;h=T,f&&f({action:d,location:S.location,delta:C})}function x(T,C){d="PUSH";let R=_x(T)?T:fp(S.location,T,C);h=b()+1;let A=Sx(R,h),D=S.createHref(R.mask||R);try{u.pushState(A,"",D)}catch(B){if(B instanceof DOMException&&B.name==="DataCloneError")throw B;i.location.assign(D)}l&&f&&f({action:d,location:S.location,delta:1})}function _(T,C){d="REPLACE";let R=_x(T)?T:fp(S.location,T,C);h=b();let A=Sx(R,h),D=S.createHref(R.mask||R);u.replaceState(A,"",D),l&&f&&f({action:d,location:S.location,delta:0})}function E(T){return sj(T)}let S={get action(){return d},get location(){return e(i,u)},listen(T){if(f)throw new Error("A history only accepts one active listener");return i.addEventListener(wx,g),f=T,()=>{i.removeEventListener(wx,g),f=null}},createHref(T){return t(i,T)},createURL:E,encodeLocation(T){let C=E(T);return{pathname:C.pathname,search:C.search,hash:C.hash}},push:x,replace:_,go(T){return u.go(T)}};return S}function sj(e,t=!1){let r="http://localhost";typeof window<"u"&&(r=window.location.origin!=="null"?window.location.origin:window.location.href),Tt(r,"No window.location.(origin|href) available to create URL");let s=typeof e=="string"?e:Cl(e);return s=s.replace(/ $/,"%20"),!t&&s.startsWith("//")&&(s=r+s),new URL(s,r)}function x_(e,t,r="/"){return ij(e,t,r,!1)}function ij(e,t,r,s,i){let l=typeof t=="string"?lo(t):t,u=ua(l.pathname||"/",r);if(u==null)return null;let d=oj(e),f=null,h=bj(u);for(let b=0;f==null&&b{let b={relativePath:h===void 0?u.path||"":h,caseSensitive:u.caseSensitive===!0,childrenIndex:d,route:u};if(b.relativePath.startsWith("/")){if(!b.relativePath.startsWith(s)&&f)return;Tt(b.relativePath.startsWith(s),`Absolute route path "${b.relativePath}" nested under path "${s}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),b.relativePath=b.relativePath.slice(s.length)}let g=yr([s,b.relativePath]),x=r.concat(b);u.children&&u.children.length>0&&(Tt(u.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${g}".`),w_(u.children,t,x,g,f)),!(u.path==null&&!u.index)&&t.push({path:g,score:pj(g,u.index),routesMeta:x})};return e.forEach((u,d)=>{var f;if(u.path===""||!((f=u.path)!=null&&f.includes("?")))l(u,d);else for(let h of __(u.path))l(u,d,!0,h)}),t}function __(e){let t=e.split("/");if(t.length===0)return[];let[r,...s]=t,i=r.endsWith("?"),l=r.replace(/\?$/,"");if(s.length===0)return i?[l,""]:[l];let u=__(s.join("/")),d=[];return d.push(...u.map(f=>f===""?l:[l,f].join("/"))),i&&d.push(...u),d.map(f=>e.startsWith("/")&&f===""?"/":f)}function lj(e){e.sort((t,r)=>t.score!==r.score?r.score-t.score:gj(t.routesMeta.map(s=>s.childrenIndex),r.routesMeta.map(s=>s.childrenIndex)))}var cj=/^:[\w-]+$/,uj=3,dj=2,fj=1,hj=10,mj=-2,Ex=e=>e==="*";function pj(e,t){let r=e.split("/"),s=r.length;return r.some(Ex)&&(s+=mj),t&&(s+=dj),r.filter(i=>!Ex(i)).reduce((i,l)=>i+(cj.test(l)?uj:l===""?fj:hj),s)}function gj(e,t){return e.length===t.length&&e.slice(0,-1).every((s,i)=>s===t[i])?e[e.length-1]-t[t.length-1]:0}function vj(e,t,r=!1){let{routesMeta:s}=e,i={},l="/",u=[];for(let d=0;d{if(b==="*"){let E=d[x]||"";u=l.slice(0,l.length-E.length).replace(/(.)\/+$/,"$1")}const _=d[x];return g&&!_?h[b]=void 0:h[b]=(_||"").replace(/%2F/g,"/"),h},{}),pathname:l,pathnameBase:u,pattern:e}}function yj(e,t=!1,r=!0){Lr(e==="*"||!e.endsWith("*")||e.endsWith("/*"),`Route path "${e}" will be treated as if it were "${e.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${e.replace(/\*$/,"/*")}".`);let s=[],i="^"+e.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(u,d,f,h,b)=>{if(s.push({paramName:d,isOptional:f!=null}),f){let g=b.charAt(h+u.length);return g&&g!=="/"?"/([^\\/]*)":"(?:/([^\\/]*))?"}return"/([^\\/]+)"}).replace(/\/([\w-]+)\?(\/|$)/g,"(/$1)?$2");return e.endsWith("*")?(s.push({paramName:"*"}),i+=e==="*"||e==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):r?i+="\\/*$":e!==""&&e!=="/"&&(i+="(?:(?=\\/|$))"),[new RegExp(i,t?void 0:"i"),s]}function bj(e){try{return e.split("/").map(t=>decodeURIComponent(t).replace(/\//g,"%2F")).join("/")}catch(t){return Lr(!1,`The URL path "${e}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${t}).`),e}}function ua(e,t){if(t==="/")return e;if(!e.toLowerCase().startsWith(t.toLowerCase()))return null;let r=t.endsWith("/")?t.length-1:t.length,s=e.charAt(r);return s&&s!=="/"?null:e.slice(r)||"/"}var xj=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;function wj(e,t="/"){let{pathname:r,search:s="",hash:i=""}=typeof e=="string"?lo(e):e,l;return r?(r=E_(r),r.startsWith("/")?l=Cx(r.substring(1),"/"):l=Cx(r,t)):l=t,{pathname:l,search:Ej(s),hash:Cj(i)}}function Cx(e,t){let r=sd(t).split("/");return e.split("/").forEach(i=>{i===".."?r.length>1&&r.pop():i!=="."&&r.push(i)}),r.length>1?r.join("/"):"/"}function km(e,t,r,s){return`Cannot include a '${e}' character in a manually specified \`to.${t}\` field [${JSON.stringify(s)}]. Please separate it out to the \`to.${r}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function _j(e){return e.filter((t,r)=>r===0||t.route.path&&t.route.path.length>0)}function S_(e){let t=_j(e);return t.map((r,s)=>s===t.length-1?r.pathname:r.pathnameBase)}function ag(e,t,r,s=!1){let i;typeof e=="string"?i=lo(e):(i={...e},Tt(!i.pathname||!i.pathname.includes("?"),km("?","pathname","search",i)),Tt(!i.pathname||!i.pathname.includes("#"),km("#","pathname","hash",i)),Tt(!i.search||!i.search.includes("#"),km("#","search","hash",i)));let l=e===""||i.pathname==="",u=l?"/":i.pathname,d;if(u==null)d=r;else{let g=t.length-1;if(!s&&u.startsWith("..")){let x=u.split("/");for(;x[0]==="..";)x.shift(),g-=1;i.pathname=x.join("/")}d=g>=0?t[g]:"/"}let f=wj(i,d),h=u&&u!=="/"&&u.endsWith("/"),b=(l||u===".")&&r.endsWith("/");return!f.pathname.endsWith("/")&&(h||b)&&(f.pathname+="/"),f}var E_=e=>e.replace(/\/\/+/g,"/"),yr=e=>E_(e.join("/")),sd=e=>e.replace(/\/+$/,""),Sj=e=>sd(e).replace(/^\/*/,"/"),Ej=e=>!e||e==="?"?"":e.startsWith("?")?e:"?"+e,Cj=e=>!e||e==="#"?"":e.startsWith("#")?e:"#"+e,Tj=class{constructor(e,t,r,s=!1){this.status=e,this.statusText=t||"",this.internal=s,r instanceof Error?(this.data=r.toString(),this.error=r):this.data=r}};function Nj(e){return e!=null&&typeof e.status=="number"&&typeof e.statusText=="string"&&typeof e.internal=="boolean"&&"data"in e}function Aj(e){let t=e.map(r=>r.route.path).filter(Boolean);return yr(t)||"/"}var C_=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function T_(e,t){let r=e;if(typeof r!="string"||!xj.test(r))return{absoluteURL:void 0,isExternal:!1,to:r};let s=r,i=!1;if(C_)try{let l=new URL(window.location.href),u=r.startsWith("//")?new URL(l.protocol+r):new URL(r),d=ua(u.pathname,t);u.origin===l.origin&&d!=null?r=d+u.search+u.hash:i=!0}catch{Lr(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:s,isExternal:i,to:r}}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");var N_=["POST","PUT","PATCH","DELETE"];new Set(N_);var Rj=["GET",...N_];new Set(Rj);var co=y.createContext(null);co.displayName="DataRouter";var kd=y.createContext(null);kd.displayName="DataRouterState";var A_=y.createContext(!1);function jj(){return y.useContext(A_)}var R_=y.createContext({isTransitioning:!1});R_.displayName="ViewTransition";var kj=y.createContext(new Map);kj.displayName="Fetchers";var Oj=y.createContext(null);Oj.displayName="Await";var ir=y.createContext(null);ir.displayName="Navigation";var Bl=y.createContext(null);Bl.displayName="Location";var ga=y.createContext({outlet:null,matches:[],isDataRoute:!1});ga.displayName="Route";var sg=y.createContext(null);sg.displayName="RouteError";var j_="REACT_ROUTER_ERROR",Mj="REDIRECT",Dj="ROUTE_ERROR_RESPONSE";function Pj(e){if(e.startsWith(`${j_}:${Mj}:{`))try{let t=JSON.parse(e.slice(28));if(typeof t=="object"&&t&&typeof t.status=="number"&&typeof t.statusText=="string"&&typeof t.location=="string"&&typeof t.reloadDocument=="boolean"&&typeof t.replace=="boolean")return t}catch{}}function Lj(e){if(e.startsWith(`${j_}:${Dj}:{`))try{let t=JSON.parse(e.slice(40));if(typeof t=="object"&&t&&typeof t.status=="number"&&typeof t.statusText=="string")return new Tj(t.status,t.statusText,t.data)}catch{}}function zj(e,{relative:t}={}){Tt(Hl(),"useHref() may be used only in the context of a component.");let{basename:r,navigator:s}=y.useContext(ir),{hash:i,pathname:l,search:u}=ql(e,{relative:t}),d=l;return r!=="/"&&(d=l==="/"?r:yr([r,l])),s.createHref({pathname:d,search:u,hash:i})}function Hl(){return y.useContext(Bl)!=null}function va(){return Tt(Hl(),"useLocation() may be used only in the context of a component."),y.useContext(Bl).location}var k_="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function O_(e){y.useContext(ir).static||y.useLayoutEffect(e)}function Ij(){let{isDataRoute:e}=y.useContext(ga);return e?Jj():Uj()}function Uj(){Tt(Hl(),"useNavigate() may be used only in the context of a component.");let e=y.useContext(co),{basename:t,navigator:r}=y.useContext(ir),{matches:s}=y.useContext(ga),{pathname:i}=va(),l=JSON.stringify(S_(s)),u=y.useRef(!1);return O_(()=>{u.current=!0}),y.useCallback((f,h={})=>{if(Lr(u.current,k_),!u.current)return;if(typeof f=="number"){r.go(f);return}let b=ag(f,JSON.parse(l),i,h.relative==="path");e==null&&t!=="/"&&(b.pathname=b.pathname==="/"?t:yr([t,b.pathname])),(h.replace?r.replace:r.push)(b,h.state,h)},[t,r,l,i,e])}y.createContext(null);function ql(e,{relative:t}={}){let{matches:r}=y.useContext(ga),{pathname:s}=va(),i=JSON.stringify(S_(r));return y.useMemo(()=>ag(e,JSON.parse(i),s,t==="path"),[e,i,s,t])}function $j(e,t){return M_(e,t)}function M_(e,t,r){var T;Tt(Hl(),"useRoutes() may be used only in the context of a component.");let{navigator:s}=y.useContext(ir),{matches:i}=y.useContext(ga),l=i[i.length-1],u=l?l.params:{},d=l?l.pathname:"/",f=l?l.pathnameBase:"/",h=l&&l.route;{let C=h&&h.path||"";P_(d,!h||C.endsWith("*")||C.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${d}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. - -Please change the parent to .`)}let b=va(),g;if(t){let C=typeof t=="string"?lo(t):t;Tt(f==="/"||((T=C.pathname)==null?void 0:T.startsWith(f)),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${f}" but pathname "${C.pathname}" was given in the \`location\` prop.`),g=C}else g=b;let x=g.pathname||"/",_=x;if(f!=="/"){let C=f.replace(/^\//,"").split("/");_="/"+x.replace(/^\//,"").split("/").slice(C.length).join("/")}let E=r&&r.state.matches.length?r.state.matches.map(C=>Object.assign(C,{route:r.manifest[C.route.id]||C.route})):x_(e,{pathname:_});Lr(h||E!=null,`No routes matched location "${g.pathname}${g.search}${g.hash}" `),Lr(E==null||E[E.length-1].route.element!==void 0||E[E.length-1].route.Component!==void 0||E[E.length-1].route.lazy!==void 0,`Matched leaf route at location "${g.pathname}${g.search}${g.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let S=Gj(E&&E.map(C=>Object.assign({},C,{params:Object.assign({},u,C.params),pathname:yr([f,s.encodeLocation?s.encodeLocation(C.pathname.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:C.pathname]),pathnameBase:C.pathnameBase==="/"?f:yr([f,s.encodeLocation?s.encodeLocation(C.pathnameBase.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:C.pathnameBase])})),i,r);return t&&S?y.createElement(Bl.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",mask:void 0,...g},navigationType:"POP"}},S):S}function Bj(){let e=Xj(),t=Nj(e)?`${e.status} ${e.statusText}`:e instanceof Error?e.message:JSON.stringify(e),r=e instanceof Error?e.stack:null,s="rgba(200,200,200, 0.5)",i={padding:"0.5rem",backgroundColor:s},l={padding:"2px 4px",backgroundColor:s},u=null;return console.error("Error handled by React Router default ErrorBoundary:",e),u=y.createElement(y.Fragment,null,y.createElement("p",null,"💿 Hey developer 👋"),y.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",y.createElement("code",{style:l},"ErrorBoundary")," or"," ",y.createElement("code",{style:l},"errorElement")," prop on your route.")),y.createElement(y.Fragment,null,y.createElement("h2",null,"Unexpected Application Error!"),y.createElement("h3",{style:{fontStyle:"italic"}},t),r?y.createElement("pre",{style:i},r):null,u)}var Hj=y.createElement(Bj,null),D_=class extends y.Component{constructor(e){super(e),this.state={location:e.location,revalidation:e.revalidation,error:e.error}}static getDerivedStateFromError(e){return{error:e}}static getDerivedStateFromProps(e,t){return t.location!==e.location||t.revalidation!=="idle"&&e.revalidation==="idle"?{error:e.error,location:e.location,revalidation:e.revalidation}:{error:e.error!==void 0?e.error:t.error,location:t.location,revalidation:e.revalidation||t.revalidation}}componentDidCatch(e,t){this.props.onError?this.props.onError(e,t):console.error("React Router caught the following error during render",e)}render(){let e=this.state.error;if(this.context&&typeof e=="object"&&e&&"digest"in e&&typeof e.digest=="string"){const r=Lj(e.digest);r&&(e=r)}let t=e!==void 0?y.createElement(ga.Provider,{value:this.props.routeContext},y.createElement(sg.Provider,{value:e,children:this.props.component})):this.props.children;return this.context?y.createElement(qj,{error:e},t):t}};D_.contextType=A_;var Om=new WeakMap;function qj({children:e,error:t}){let{basename:r}=y.useContext(ir);if(typeof t=="object"&&t&&"digest"in t&&typeof t.digest=="string"){let s=Pj(t.digest);if(s){let i=Om.get(t);if(i)throw i;let l=T_(s.location,r);if(C_&&!Om.get(t))if(l.isExternal||s.reloadDocument)window.location.href=l.absoluteURL||l.to;else{const u=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(l.to,{replace:s.replace}));throw Om.set(t,u),u}return y.createElement("meta",{httpEquiv:"refresh",content:`0;url=${l.absoluteURL||l.to}`})}}return e}function Vj({routeContext:e,match:t,children:r}){let s=y.useContext(co);return s&&s.static&&s.staticContext&&(t.route.errorElement||t.route.ErrorBoundary)&&(s.staticContext._deepestRenderedBoundaryId=t.route.id),y.createElement(ga.Provider,{value:e},r)}function Gj(e,t=[],r){let s=r==null?void 0:r.state;if(e==null){if(!s)return null;if(s.errors)e=s.matches;else if(t.length===0&&!s.initialized&&s.matches.length>0)e=s.matches;else return null}let i=e,l=s==null?void 0:s.errors;if(l!=null){let b=i.findIndex(g=>g.route.id&&(l==null?void 0:l[g.route.id])!==void 0);Tt(b>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(l).join(",")}`),i=i.slice(0,Math.min(i.length,b+1))}let u=!1,d=-1;if(r&&s){u=s.renderFallback;for(let b=0;b=0?i=i.slice(0,d+1):i=[i[0]];break}}}}let f=r==null?void 0:r.onError,h=s&&f?(b,g)=>{var x,_;f(b,{location:s.location,params:((_=(x=s.matches)==null?void 0:x[0])==null?void 0:_.params)??{},pattern:Aj(s.matches),errorInfo:g})}:void 0;return i.reduceRight((b,g,x)=>{let _,E=!1,S=null,T=null;s&&(_=l&&g.route.id?l[g.route.id]:void 0,S=g.route.errorElement||Hj,u&&(d<0&&x===0?(P_("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),E=!0,T=null):d===x&&(E=!0,T=g.route.hydrateFallbackElement||null)));let C=t.concat(i.slice(0,x+1)),R=()=>{let A;return _?A=S:E?A=T:g.route.Component?A=y.createElement(g.route.Component,null):g.route.element?A=g.route.element:A=b,y.createElement(Vj,{match:g,routeContext:{outlet:b,matches:C,isDataRoute:s!=null},children:A})};return s&&(g.route.ErrorBoundary||g.route.errorElement||x===0)?y.createElement(D_,{location:s.location,revalidation:s.revalidation,component:S,error:_,children:R(),routeContext:{outlet:null,matches:C,isDataRoute:!0},onError:h}):R()},null)}function ig(e){return`${e} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function Fj(e){let t=y.useContext(co);return Tt(t,ig(e)),t}function Kj(e){let t=y.useContext(kd);return Tt(t,ig(e)),t}function Yj(e){let t=y.useContext(ga);return Tt(t,ig(e)),t}function og(e){let t=Yj(e),r=t.matches[t.matches.length-1];return Tt(r.route.id,`${e} can only be used on routes that contain a unique "id"`),r.route.id}function Wj(){return og("useRouteId")}function Xj(){var s;let e=y.useContext(sg),t=Kj("useRouteError"),r=og("useRouteError");return e!==void 0?e:(s=t.errors)==null?void 0:s[r]}function Jj(){let{router:e}=Fj("useNavigate"),t=og("useNavigate"),r=y.useRef(!1);return O_(()=>{r.current=!0}),y.useCallback(async(i,l={})=>{Lr(r.current,k_),r.current&&(typeof i=="number"?await e.navigate(i):await e.navigate(i,{fromRouteId:t,...l}))},[e,t])}var Tx={};function P_(e,t,r){!t&&!Tx[e]&&(Tx[e]=!0,Lr(!1,r))}y.memo(Qj);function Qj({routes:e,manifest:t,future:r,state:s,isStatic:i,onError:l}){return M_(e,void 0,{manifest:t,state:s,isStatic:i,onError:l})}function L_(e){Tt(!1,"A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .")}function Zj({basename:e="/",children:t=null,location:r,navigationType:s="POP",navigator:i,static:l=!1,useTransitions:u}){Tt(!Hl(),"You cannot render a inside another . You should never have more than one in your app.");let d=e.replace(/^\/*/,"/"),f=y.useMemo(()=>({basename:d,navigator:i,static:l,useTransitions:u,future:{}}),[d,i,l,u]);typeof r=="string"&&(r=lo(r));let{pathname:h="/",search:b="",hash:g="",state:x=null,key:_="default",mask:E}=r,S=y.useMemo(()=>{let T=ua(h,d);return T==null?null:{location:{pathname:T,search:b,hash:g,state:x,key:_,mask:E},navigationType:s}},[d,h,b,g,x,_,s,E]);return Lr(S!=null,` is not able to match the URL "${h}${b}${g}" because it does not start with the basename, so the won't render anything.`),S==null?null:y.createElement(ir.Provider,{value:f},y.createElement(Bl.Provider,{children:t,value:S}))}function ek({children:e,location:t}){return $j(hp(e),t)}function hp(e,t=[]){let r=[];return y.Children.forEach(e,(s,i)=>{if(!y.isValidElement(s))return;let l=[...t,i];if(s.type===y.Fragment){r.push.apply(r,hp(s.props.children,l));return}Tt(s.type===L_,`[${typeof s.type=="string"?s.type:s.type.name}] is not a component. All component children of must be a or `),Tt(!s.props.index||!s.props.children,"An index route cannot have child routes.");let u={id:s.props.id||l.join("-"),caseSensitive:s.props.caseSensitive,element:s.props.element,Component:s.props.Component,index:s.props.index,path:s.props.path,middleware:s.props.middleware,loader:s.props.loader,action:s.props.action,hydrateFallbackElement:s.props.hydrateFallbackElement,HydrateFallback:s.props.HydrateFallback,errorElement:s.props.errorElement,ErrorBoundary:s.props.ErrorBoundary,hasErrorBoundary:s.props.hasErrorBoundary===!0||s.props.ErrorBoundary!=null||s.props.errorElement!=null,shouldRevalidate:s.props.shouldRevalidate,handle:s.props.handle,lazy:s.props.lazy};s.props.children&&(u.children=hp(s.props.children,l)),r.push(u)}),r}var Ku="get",Yu="application/x-www-form-urlencoded";function Od(e){return typeof HTMLElement<"u"&&e instanceof HTMLElement}function tk(e){return Od(e)&&e.tagName.toLowerCase()==="button"}function nk(e){return Od(e)&&e.tagName.toLowerCase()==="form"}function rk(e){return Od(e)&&e.tagName.toLowerCase()==="input"}function ak(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function sk(e,t){return e.button===0&&(!t||t==="_self")&&!ak(e)}var bu=null;function ik(){if(bu===null)try{new FormData(document.createElement("form"),0),bu=!1}catch{bu=!0}return bu}var ok=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function Mm(e){return e!=null&&!ok.has(e)?(Lr(!1,`"${e}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${Yu}"`),null):e}function lk(e,t){let r,s,i,l,u;if(nk(e)){let d=e.getAttribute("action");s=d?ua(d,t):null,r=e.getAttribute("method")||Ku,i=Mm(e.getAttribute("enctype"))||Yu,l=new FormData(e)}else if(tk(e)||rk(e)&&(e.type==="submit"||e.type==="image")){let d=e.form;if(d==null)throw new Error('Cannot submit a + + {folderUrl && folderUrl !== mainBriefUrl && ( + + )} + +
+ {!isRejected && ( + + )} + +
+ + + + ); +} + +function InfoRow({ icon, label, value }: { icon: React.ReactNode; label: string; value: string }) { + return ( +
+ {icon} + {label} + {value || "—"} +
+ ); +} diff --git a/src/components/board/ProjectCard.tsx b/src/components/board/ProjectCard.tsx index 1edea5f..570c590 100644 --- a/src/components/board/ProjectCard.tsx +++ b/src/components/board/ProjectCard.tsx @@ -1,19 +1,29 @@ -import { MapPin, User, Tag, Link2, FileCheck2, CheckCircle2 } from "lucide-react"; +import { MapPin, User, Tag, Link2, FileCheck2, CheckCircle2, Clock3 } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { cn } from "@/lib/utils"; import { colorHex } from "@/lib/colors"; import type { Project } from "@/lib/store"; -import { useAuth } from "@/context/AuthContext"; interface ProjectCardProps { project: Project; onClick: () => void; + onTimeClick?: () => void; + showInternalAmount?: boolean; } -export function ProjectCard({ project, onClick }: ProjectCardProps) { +export function ProjectCard({ + project, + onClick, + onTimeClick, + showInternalAmount = false, +}: ProjectCardProps) { const closed = !!project.status; - const { isGerardo } = useAuth(); - + const internalAmount = Number(project.monto); + const hasInternalAmount = + showInternalAmount && + project.monto !== null && + Number.isFinite(internalAmount) && + internalAmount > 0; const handleKeyDown = (event: React.KeyboardEvent) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); @@ -23,6 +33,9 @@ export function ProjectCard({ project, onClick }: ProjectCardProps) { return (
} label="Solicita" value={project.solicitante} />
-
+
+ {onTimeClick && ( + + )} {project.propuestaLinks.length > 0 && ( {project.propuestaLinks.length} @@ -78,12 +106,16 @@ export function ProjectCard({ project, onClick }: ProjectCardProps) { )}
-
- {isGerardo && project.monto != null && ( - - ${project.monto.toLocaleString()} +
+ {hasInternalAmount && ( + + {formatInternalAmount(internalAmount)} )} + {project.status && ( diff --git a/src/components/board/ProjectDialog.tsx b/src/components/board/ProjectDialog.tsx index 8ac933e..b0e65c4 100644 --- a/src/components/board/ProjectDialog.tsx +++ b/src/components/board/ProjectDialog.tsx @@ -1,4 +1,4 @@ -import { useCallback, useState, useEffect } from "react"; +import { useCallback, useRef, useState, useEffect } from "react"; import { Dialog, DialogContent, @@ -25,7 +25,6 @@ import { AlertCircle, Loader2, Lock, - DollarSign, Calendar, Trash2, Search, @@ -69,6 +68,7 @@ interface Props { } const WALMART_CONNECT_CLIENT = "Walmart Connect WMC"; +const WMC_CREATE_EXCLUDED_BU = new Set(["wmc"]); const ACTIVITY_PAGE_SIZE = 15; function formatActivityDate(timestamp: number) { @@ -107,6 +107,8 @@ function toPricingInputItems( referenceMax: item.referenceMax, amount: item.amount, description: item.description, + tariffSectionId: item.tariffSectionId, + tariffCatalogId: item.tariffCatalogId, })); } @@ -261,6 +263,7 @@ export function ProjectDialog({ open, onOpenChange, project, onDelete, deleting const [pricingLoading, setPricingLoading] = useState(false); const [pricingError, setPricingError] = useState(null); const [internalAmountManuallyEdited, setInternalAmountManuallyEdited] = useState(false); + const saveInFlightRef = useRef(false); const selectedBrands = splitBrandValues(form.marca, lists.marcas); const selectedCountries = splitCountryValues(form.bu, lists.bus); @@ -268,9 +271,10 @@ export function ProjectDialog({ open, onOpenChange, project, onDelete, deleting const marcaOptions = dedupeOptions([...lists.marcas, ...selectedBrands]); const buOptions = dedupeOptions([...lists.bus, ...selectedCountries]); const statusOptions = dedupeOptions(ensureOption(lists.status, form.status)) as Status[]; - const filteredWmcBuOptions = buOptions.filter((b) => - normalizeOptionKey(b).includes(normalizeOptionKey(wmcCountrySearch)), - ); + const filteredWmcBuOptions = buOptions.filter((b) => { + const key = normalizeOptionKey(b); + return !WMC_CREATE_EXCLUDED_BU.has(key) && key.includes(normalizeOptionKey(wmcCountrySearch)); + }); const pricingTotal = getPricingTotal(pricingItems); useEffect(() => { @@ -399,6 +403,19 @@ export function ProjectDialog({ open, onOpenChange, project, onDelete, deleting const set = (k: K, v: (typeof form)[K]) => setForm((f) => ({ ...f, [k]: v })); + // El tarifario es la única fuente editable del monto interno. + // Conservamos overrides históricos al abrir un proyecto, pero en cuanto + // el usuario modifica las líneas del tarifario el total vuelve a ser la fuente de verdad. + const onPricingItemsChange = (nextItems: ProjectPricingItemInput[]) => { + setInternalAmountManuallyEdited(false); + setPricingItems(nextItems); + + const nextAmount = Number(getPricingTotal(nextItems).toFixed(2)); + setForm((current) => + current.monto === nextAmount ? current : { ...current, monto: nextAmount }, + ); + }; + useEffect(() => { if (!canManageInternalPricing || !pricingItems.length || internalAmountManuallyEdited) return; @@ -540,7 +557,9 @@ export function ProjectDialog({ open, onOpenChange, project, onDelete, deleting }; const submit = async () => { - if (!requiredOk || saving) return; + if (!requiredOk || saving || saveInFlightRef.current) return; + + saveInFlightRef.current = true; const safeForm = isGerardo ? form @@ -587,7 +606,7 @@ export function ProjectDialog({ open, onOpenChange, project, onDelete, deleting if (failedSyncs.length === 0) { toast.success("Proyectos multipaís creados", { - description: `Se crearon ${countries.length} proyectos y ${countries.length} filas en Google Sheet. El Interno Cargado se aplicó por país.`, + description: `Se crearon ${countries.length} proyectos y ${countries.length} filas en Google Sheet. El total de la estimación interna se aplicó por país.`, }); } else { toast.warning("Proyectos multipaís creados con aviso", { @@ -624,6 +643,7 @@ export function ProjectDialog({ open, onOpenChange, project, onDelete, deleting description: message, }); } finally { + saveInFlightRef.current = false; setSaving(false); setSavingStep(null); } @@ -632,7 +652,13 @@ export function ProjectDialog({ open, onOpenChange, project, onDelete, deleting const closed = !!form.status; return ( - + { + if (saving || saveInFlightRef.current) return; + onOpenChange(nextOpen); + }} + > {/* Pestaña de color superior */}
- Si seleccionas varios países, el Interno Cargado se aplicará por cada país. La app + Si seleccionas varios países, el total de la estimación interna se aplicará por cada país. La app no divide el monto automáticamente.
)} @@ -940,8 +966,9 @@ export function ProjectDialog({ open, onOpenChange, project, onDelete, deleting {canManageInternalPricing && ( @@ -986,32 +1013,6 @@ export function ProjectDialog({ open, onOpenChange, project, onDelete, deleting )}
- {/* Monto: SOLO visible para usuarios internos autorizados */} - {canManageInternalPricing && ( -
- - { - setInternalAmountManuallyEdited(true); - set("monto", e.target.value === "" ? null : Number(e.target.value)); - }} - placeholder="0.00" - className="h-10" - /> -

- Privado · solo usuarios internos autorizados. - {internalAmountManuallyEdited && pricingItems.length > 0 - ? " Monto ajustado manualmente; se respetará al guardar." - : pricingItems.length > 0 - ? " El total del tarifario se refleja aquí y puedes ajustarlo." - : ""} -

-
- )}
{(formError || savingStep) && ( @@ -1057,7 +1058,10 @@ export function ProjectDialog({ open, onOpenChange, project, onDelete, deleting