Files
inq-roi-simulador-web/sprints/sprint-8/ETAPA_4_5_PROMPT.md
Marcos Benítez 54306be1c2
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Etapa 4.5 entregada.
2026-07-07 22:02:19 -03:00

8.2 KiB

Sprint 8 — Etapa 4.5: Fixes de navegación e InQuality

Tipo: UI — fixes puntuales Rama: main Estimación: 1 hora Dependencias: Etapa 4 completada


Objetivo

Dos fixes independientes detectados en validación de Etapa 4:

  1. InQuality invisible en admin: OrganizationsPage filtra con .eq('is_provider', false), dejando fuera a InQuality. El equipo no puede gestionar sus propias áreas ni procesos desde el panel. Fix: mostrar todas las orgs, con un badge visual para InQuality.

  2. Back button sin contexto de org: En el workspace, el botón "← Solar Banco" (o cualquier org) navega a /library sin parámetros — el usuario llega al home genérico en lugar de la sección de esa org. Fix: pasar ?org=<orgId> al navegar y hacer scroll al bloque correcto en LibraryPage.


Fix 1: OrganizationsPage.tsx

Problema actual

Línea en fetchOrgs():

.eq('is_provider', false)

Esta condición excluye a InQuality (que tiene is_provider = true) de la lista. El equipo de InQuality no puede entrar a /admin/organizations/:id de su propia org para gestionar áreas y procesos.

Cambios en OrganizationsPage.tsx

1A. Extender la interface OrgRow:

interface OrgRow {
  id: string
  name: string
  is_provider: boolean   // 🆕
  created_at: string
  processes: [{ count: number }]
  users: [{ count: number }]
}

1B. Actualizar fetchOrgs() — quitar el filtro y agregar is_provider al select:

const fetchOrgs = async () => {
  const { data } = await supabase
    .from('organizations')
    .select('id, name, is_provider, created_at, processes:processes(count), users:users(count)')
    // ← eliminar el .eq('is_provider', false)
    .order('name')
  
  // Ordenar: proveedor primero, luego clientes alfabético
  const sorted = ((data as OrgRow[] | null) ?? []).sort((a, b) => {
    if (a.is_provider && !b.is_provider) return -1
    if (!a.is_provider && b.is_provider) return 1
    return a.name.localeCompare(b.name)
  })
  
  setOrgs(sorted)
  setLoading(false)
}

1C. Actualizar el título:

// Antes:
<h2 className="text-lg font-semibold text-slate-900">Organizaciones clientes</h2>

// Después:
<h2 className="text-lg font-semibold text-slate-900">Organizaciones</h2>

1D. Agregar badge "Proveedor" en la columna de nombre:

En la fila de la tabla, en la celda de nombre:

<td className="py-3 px-4 font-medium text-slate-900">
  <div className="flex items-center gap-2">
    {org.name}
    {org.is_provider && (
      <span className="text-[10px] font-semibold px-1.5 py-0.5 rounded-full border"
        style={{ color: '#F59845', background: '#fff8f0', borderColor: '#fcd9aa' }}>
        InQuality
      </span>
    )}
  </div>
</td>

No cambiar el botón "Ver detalle →" — funciona igual para InQuality que para cualquier otra org.


Fix 2: Back button con contexto de org

Son tres cambios coordinados: router.tsx, WorkspacePage.tsx, y LibraryPage.tsx.

Fix 2A: router.tsx — declarar org como search param de /library

TanStack Router requiere que los search params estén declarados en la ruta para que navigate({ search: ... }) sea type-safe.

// Antes:
const libraryRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/library',
  component: LibraryPage,
})

// Después:
const libraryRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/library',
  component: LibraryPage,
  validateSearch: (search: Record<string, unknown>) => ({
    org: typeof search.org === 'string' ? search.org : undefined,
  }),
})

NO tocar indexRoute (/) — no necesita el param porque nunca se navega a / desde el workspace.

Fix 2B: WorkspacePage.tsx — pasar org en handleBack

// Antes (línea ~194-196):
function handleBack() {
  void navigate({ to: '/library' })
}

// Después:
function handleBack() {
  void navigate({
    to: '/library',
    search: currentProcess.orgId ? { org: currentProcess.orgId } : {},
  })
}

Fix 2C: LibraryPage.tsx — leer param y hacer scroll

Imports: agregar useLocation al import existente de @tanstack/react-router:

// Antes:
import { useNavigate } from '@tanstack/react-router'

// Después:
import { useNavigate, useLocation } from '@tanstack/react-router'

Leer el param (agregar dentro del cuerpo de LibraryPage, junto a los otros hooks):

const location = useLocation()
const orgParam = useMemo(
  () => new URLSearchParams(location.search).get('org') ?? undefined,
  [location.search]
)

useMemo ya está importado en el archivo.

Efecto de scroll (agregar después de los efectos existentes):

// Scroll a la sección del org si venimos del workspace
useEffect(() => {
  if (!orgParam || isLoading || processes.length === 0) return
  requestAnimationFrame(() => {
    const el = document.getElementById(`org-section-${orgParam}`)
    el?.scrollIntoView({ behavior: 'smooth', block: 'start' })
  })
}, [orgParam, isLoading, processes.length])

Agregar id al div de cada org en el bloque admin (en el orgGroups.map(...)):

// Antes:
<div key={orgId} className="border border-border rounded-xl overflow-hidden">

// Después:
<div key={orgId} id={`org-section-${orgId}`} className="border border-border rounded-xl overflow-hidden">

Solo en el div raíz de cada org (el que tiene el key={orgId}). No tocar divs internos.


Lo que NO cambia en esta etapa

  • src/features/admin/OrganizationDetailPage.tsx — sin cambios; el detalle de InQuality ya funciona una vez que aparece en la lista.
  • src/store/library-store.ts — sin cambios.
  • Cualquier otro archivo — sin cambios.

Criterios de validación

Fix 1 — InQuality visible

  • La página /admin/organizations muestra a InQuality en la lista
  • InQuality aparece primero (antes que las orgs clientes)
  • InQuality tiene el badge naranja "InQuality"
  • Al hacer click en "Ver detalle →" de InQuality, lleva a su detalle con procesos, usuarios y áreas
  • Las otras orgs clientes siguen apareciendo igual que antes

Fix 2 — Back button con scroll

  • Abrir un proceso de "Solar Banco" → workspace → botón "← Solar Banco" → navega a /library?org=<orgId>
  • Al llegar a /library, la página hace scroll automático al bloque de "Solar Banco"
  • El bloque de "Solar Banco" está expandido (todas las orgs empiezan expandidas por default)
  • Si el proceso no tiene org (edge case), el back button va a /library sin param y no hay scroll

Técnicos

  • npm run build sin errores TypeScript
  • npm run test sin regresiones (596 tests)

Restricciones explícitas

  • NO agregar validateSearch a indexRoute (/) — solo a libraryRoute
  • NO modificar OrganizationDetailPage.tsx en esta etapa
  • NO cambiar el comportamiento del botón "Nueva organización" — InQuality se crea con is_provider: false, InQuality nunca se crea desde la UI (es dato de seeding)
  • NO agregar tests en esta etapa

Archivos autorizados a modificar

src/
├── router.tsx                               ← validateSearch en libraryRoute
├── features/
│   ├── workspace/
│   │   └── WorkspacePage.tsx                ← handleBack con search: { org }
│   ├── library/
│   │   └── LibraryPage.tsx                  ← useLocation + scroll effect + id en org divs
│   └── admin/
│       └── OrganizationsPage.tsx            ← quitar filtro is_provider + badge + título

Commits esperados

fix(admin): InQuality visible en OrganizationsPage con badge — quitar filtro is_provider
fix(workspace): handleBack pasa ?org=orgId para scroll contextual en biblioteca
fix(library): scroll a sección de org al volver del workspace con ?org param

Reporte esperado

  1. Confirmar que InQuality aparece en la lista de organizaciones con el badge
  2. Confirmar que el back button del workspace pasa ?org= en la URL
  3. Confirmar que la biblioteca hace scroll al bloque correcto al recibir el param
  4. npm run build y npm run test (con conteo)
  5. Solicitar validación visual del director en los dos fixes antes del OK formal