This commit is contained in:
266
sprints/sprint-8/ETAPA_4_5_PROMPT.md
Normal file
266
sprints/sprint-8/ETAPA_4_5_PROMPT.md
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
# 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()`:
|
||||||
|
```typescript
|
||||||
|
.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`:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
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:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
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:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// 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:
|
||||||
|
```tsx
|
||||||
|
<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.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 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`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 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`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 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):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
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):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 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(...)`):
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// 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
|
||||||
Reference in New Issue
Block a user