Fase 2: 04_sanitize.py - scrubbing de secretos de skills/agentes/plans (10 secretos unicos, gate en verde)
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
# 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 `<TourProvider>`. 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_<column>_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.
|
||||
Reference in New Issue
Block a user