# GFIBER-673 — Propuesta de tutorial in-app con reactour ## Contexto GFIBER-673 es un spike: "¿cómo se vería un tutorial paso a paso al primer login o tras un release mayor, para Admins y para Users?" No existe hoy ningún componente de tour/onboarding en el repo (confirmado por grep exhaustivo). El usuario propuso evaluar [reactour](https://docs.reactour.dev/) como librería base. Esta propuesta cubre: (1) el veredicto de viabilidad técnica, (2) el contenido concreto del tour para cada rol, anclado en pantallas reales del repo, y (3) el enfoque de implementación e integración con las convenciones ya establecidas del proyecto. ## Veredicto de viabilidad: apto, sin bloqueantes | Chequeo | Requisito de reactour | Este repo | Resultado | |---|---|---|---| | React | `16.x \| 17.x \| 18.x \| 19.x` (peer dep) | React 19.2.4 (pinneado) | Compatible | | Next.js / router | Necesita boundary `'use client'` (hooks + DOM) | Next.js 16.2.7, App Router, 57 archivos ya usan `'use client'` | Patrón ya establecido | | TypeScript | Reescrito en TS nativo | TS 5 strict | Compatible | | Estilos | Popover/mask vía portal, estilable por props inline | Tailwind v4 (`app/`) + SCSS Modules (`packages/design-system`), sin Radix/MUI/Headless UI | Sin conflicto de dependencias | | Persistencia "ya lo vio" | Fuera del alcance de la librería | Convención ya usada: columna boolean en `profiles` vía migración (`is_enabled`, `notify_pending_attendance`) | Patrón reusable directo | | Paquete | `@reactour/tour` v3.8.0, MIT, activo (4.1k★) | — | Apto | **Dos matices para la implementación** (no bloquean el spike, pero condicionan el enfoque): 1. reactour apunta pasos por selector CSS — usar atributos dedicados `data-tour="..."` en los elementos objetivo, nunca clases de Tailwind (frágil ante cambios de diseño). 2. El theming de reactour es vía objetos de estilo inline, no clases Tailwind — se puede lograr consistencia visual referenciando directamente las custom properties `--fiber-*` ya definidas en `app/globals.css` (ej. `backgroundColor: 'var(--fiber-primary)'`), sin depender de Tailwind dentro del portal de reactour. **Fuera de alcance de este spike**: Triple T no existe en este repositorio (confirmado — solo aparece mencionado como "fuera de alcance" en `docs/plans/*`). El AC del ticket pide tours por **rol** (Admin / User), no por producto, así que esto no bloquea la propuesta — se deja anotado como gap a resolver el día que Triple T viva en este monorepo o se integre. ## Hallazgo clave que moldea el diseño Todos los roles — Admin, Owner y User — aterrizan en la **misma página tras login**: `/upsell-evaluator` (confirmado: `app/page.tsx` redirige a `/login`, y el wordmark del NavBar en `components/NavBar.tsx:38` linkea `/upsell-evaluator` como "GFiber Pilot home" para todos). No existe un "home de admin" separado. Esto significa que un tour "por rol" en el sentido de páginas de entrada distintas no aplica — en cambio, el diseño correcto es: - **Un tour base común** (Upsell Evaluator) que ven Admin y User por igual, la primera vez que llegan a `/upsell-evaluator`. - **Un tour adicional, solo-Admin/Owner**, que se dispara la primera vez que visitan `/admin` o `/dashboard` — páginas que ni siquiera son visibles en el NavBar para un User raso (`canAccessAdmin` en `components/NavBar.tsx`). Esto respeta el AC del ticket ("proponer tutorial para Admins" + "proponer tutorial para Users") sin inventar una página de entrada que no existe. ## Propuesta — Tour de Users (y base para Admins) Disparado la primera vez que cualquier usuario autenticado visita `/upsell-evaluator` (`app/(protected)/upsell-evaluator/page.tsx`). | # | Elemento objetivo (`data-tour`) | Componente real | Contenido del paso | |---|---|---|---| | 1 | `nav-wordmark` | `components/NavBar.tsx` | "Este es tu punto de partida — GFiber Pilot home." | | 2 | `stage-transition-gaid` | `_flow/StageTransition.tsx` | "Ingresa el GAID y Case ID del cliente, luego presiona Check para validar elegibilidad." | | 3 | `stage-transition-hardstops` | Chips de riesgo (Financial Sensitivity, Technical Instability, Negative Sentiment) | "Marca estos chips si detectas alguna señal de riesgo antes de continuar." | | 4 | `objection-drawer-trigger` | `ObjectionDrawer.tsx` | "En cualquier momento de la llamada, abre este panel para scripts de manejo de objeciones." | | 5 | `copilot-widget-launcher` | `components/upsell/CopilotWidget.tsx` | "¿Duda sobre qué responder? Pega el chat del cliente aquí y el asistente sugiere una respuesta." | | 6 | `stage-discovery-quickcapture` | `_flow/StageDiscovery.tsx` | "Usa estos chips rápidos (Gaming, WFH, Cámaras...) o profundiza en el acordeón de abajo." | | 7 | `stage-value-comparison` | `_flow/StageValue.tsx` | "Aquí comparas el plan actual del cliente contra el upgrade recomendado, con costo diario." | | 8 | `stage-close-outcome` | `_flow/StageClose.tsx` | "Registra el resultado (Aceptado/Declinado); si aceptó, la nota para Salesforce se genera sola." | | 9 | `navbar-my-metrics` | Link "My Metrics" en `components/NavBar.tsx` | "Aquí revisas tu historial de sesiones y métricas personales." | **Persistencia**: nueva columna `has_seen_upsell_tour boolean not null default false` en `profiles`, siguiendo el patrón exacto de `supabase/migrations/20260625000000_add_notify_pending_attendance_to_profiles.sql`. Se marca `true` vía Server Action al completar o saltar el tour. ## Propuesta — Tour adicional de Admins/Owners Disparado la primera vez que un usuario con `role IN ('admin', 'owner')` visita `/admin` (y, por separado, la primera vez que visita `/dashboard`) — ambos gateados hoy por `app/(protected)/admin/layout.tsx` y `app/(protected)/dashboard/layout.tsx`. **Parte A — `/admin` (User Management)** | # | Elemento objetivo | Componente real | Contenido del paso | |---|---|---|---| | 1 | `admin-invite-form` | `components/admin/InviteUserForm.tsx` | "Invita usuarios nuevos por email, asignando su rol desde aquí." | | 2 | `admin-user-table` | `components/admin/UserTable.tsx` | "Esta tabla lista a todos los usuarios; usa '⋯ More' para gestionar cada uno." | | 3 | `admin-manage-drawer` | `components/admin/UserManageDrawer.tsx` | "Desde aquí cambias rol, equipo, activas/desactivas o reenvías la invitación." | **Parte B — `/dashboard` (Sales Dashboard)** | # | Elemento objetivo | Componente real | Contenido del paso | |---|---|---|---| | 1 | `dashboard-date-range` | `DateRangeControl` | "Filtra todas las métricas por rango de fechas." | | 2 | `dashboard-team-toggle` | `TeamToggle` | "Alterna entre ver solo tu equipo o todos los equipos." | | 3 | `dashboard-leaderboard` | `Leaderboard` / `AllTeamsCard` | "Aquí comparas el desempeño de agentes y equipos." | | 4 | `dashboard-export` | Botón "Export report" (`app/(protected)/dashboard/api/export/route.ts`) | "Descarga el reporte completo en un clic." | **Persistencia**: columna `has_seen_admin_tour boolean not null default false` en `profiles` — mismo patrón de migración, solo relevante/seteable para `admin`/`owner`. ## Controles de usuario: Skip y reinicio manual Ambos tours (Users y Admin/Owner) deben poder **saltarse** y **reiniciarse a demanda** — no solo dispararse una vez de forma automática: - **Skip**: cada paso del popover incluye un botón "Saltar tutorial" (override del componente `Close`/footer de reactour, vía la prop `components` de `TourProvider`), visible desde el primer paso — no solo al final. Al presionarlo, `setIsOpen(false)` cierra el tour **y** dispara la misma Server Action que marca `has_seen_upsell_tour` / `has_seen_admin_tour` en `true`, para que no se vuelva a mostrar automáticamente en el siguiente login. - **Reinicio manual**: un botón persistente para volver a lanzar el tour cuando el usuario quiera, sin depender del flag de "ya lo vio": - **Tour de Users** → ícono "? Ver tutorial" junto al wordmark en `components/NavBar.tsx`, visible para todos los roles. Llama `setIsOpen(true)` reseteando `currentStep` a 0, sin tocar el flag en `profiles` (reiniciar no debe tener efectos secundarios de persistencia). - **Tour de Admin** → mismo patrón de botón "? Ver tutorial", ubicado en el header de `app/(protected)/admin/page.tsx` y de `app/(protected)/dashboard/page.tsx` respectivamente (cada página relanza solo su propia mitad del tour — Parte A o Parte B). Esto significa que el `TourProvider` necesita exponer el hook `useTour` no solo para auto-arranque en el layout, sino también consumible desde estos botones — encaja de forma natural con la API de reactour (`useTour()` es válido en cualquier client component descendiente del `TourProvider`). ## Enfoque técnico de implementación (para cuando se apruebe pasar de spike a build) 1. Instalar `@reactour/tour`. 2. Nuevo client component `components/ProductTour.tsx` (`'use client'`), envolviendo el contenido de `app/(protected)/layout.tsx` con ``. Recibe `profile.role`, `profile.has_seen_upsell_tour`, `profile.has_seen_admin_tour` como props desde el server component padre (ya se resuelve ahí vía `getCurrentUserWithProfile()`). 3. Dos arrays de `steps` (`upsellTourSteps`, `adminTourSteps`) definidos por selector `data-tour`, no por clase Tailwind. 4. Botón "? Ver tutorial" reutilizable (`components/TourRestartButton.tsx`) que consume `useTour()` — instanciado en `NavBar.tsx` (tour de Users) y en los headers de `/admin` y `/dashboard` (tour de Admin). 5. Override del footer/`Close` de reactour vía la prop `components` de `TourProvider`, agregando la acción "Saltar tutorial" en todos los pasos. 6. Dos migraciones nuevas en `supabase/migrations/`, siguiendo el formato `YYYYMMDDHHMMSS_add__to_profiles.sql`. 7. Server Action para marcar cada flag `true` al completar **o saltar** el tour respectivo (patrón ya usado para `notify_pending_attendance`); el botón de reinicio manual NO llama a esta acción. 8. Theming del popover/mask vía las custom properties `--fiber-*` existentes en `app/globals.css`, para que visualmente no desentone con el resto de la UI. 9. Spike técnico corto (≈30 min) antes de comprometerse: confirmar si los componentes internos de reactour (`Wrapper`, `Badge`, `Close`) aceptan override completo o solo estilos inline — determina cuánto se puede "tailwindizar" el look del tour, incluyendo el botón de Skip. ## Verificación - Levantar el repo local, loguear como `user` → confirmar que el tour de Upsell Evaluator se dispara una sola vez (flag persiste tras reload). - Loguear como `admin`/`owner` → confirmar que ven el tour base de Upsell Evaluator y, al visitar `/admin` y `/dashboard` por primera vez, el tour adicional. - Confirmar que un `user` sin acceso a `/admin` nunca ve ese tour (ni el flag se crea/lee para él de forma que rompa nada). - Revisar visualmente que el popover de reactour respeta paleta y modo oscuro (`--fiber-*`, variante `.dark`). - Presionar "Saltar tutorial" en el paso 1 → confirmar que el tour se cierra y el flag correspondiente queda en `true` (no vuelve a auto-abrirse en el siguiente login). - Con el flag ya en `true`, presionar el botón "? Ver tutorial" en NavBar/`/admin`/`/dashboard` → confirmar que el tour se relanza desde el paso 1 sin alterar el flag. ## Entrega Publicar esta propuesta como página en el space de Docmost del proyecto (`gfiber-pilot-extension`), como respuesta al spike GFIBER-673. Se enlaza el ticket de Jira en la página.