From 7da39c7f1e69ac79d5ffd3a9f286593083aef950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Ben=C3=ADtez?= Date: Sat, 20 Jun 2026 20:08:23 -0300 Subject: [PATCH] prompt --- .../sprints/sprint-5/ETAPA_4G_PROMPT.md | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 cowork/InQ ROI - Simulador de procesos/sprints/sprint-5/ETAPA_4G_PROMPT.md diff --git a/cowork/InQ ROI - Simulador de procesos/sprints/sprint-5/ETAPA_4G_PROMPT.md b/cowork/InQ ROI - Simulador de procesos/sprints/sprint-5/ETAPA_4G_PROMPT.md new file mode 100644 index 0000000..d5b4c25 --- /dev/null +++ b/cowork/InQ ROI - Simulador de procesos/sprints/sprint-5/ETAPA_4G_PROMPT.md @@ -0,0 +1,191 @@ +# Sprint 5 — Etapa 4G: Revert ProfileSheet a raw fetch + +## Contexto + +InQ ROI — React 18 + Vite + TypeScript + Supabase v2. + +El commit anterior cambió ProfileSheet.tsx para usar `supabase.from('users').update()` en lugar +de raw fetch. Eso introdujo una regresión: el save completa (204) pero la UI se congela +porque `supabase.from().update()` adquiere processLock internamente vía getSession(). Mientras +el lock está ocupado, otras queries Supabase de la página esperan → UI congelada. + +La versión anterior (raw fetch con token de localStorage) fue confirmada funcionando: 204, sin +freeze. Esta etapa revierte a esa estrategia. + +La función `getAccessToken()` ya no existe en el codebase — hay que recrearla inline en el archivo. + +--- + +## Cambio único: `src/components/ProfileSheet.tsx` + +Reemplazar el archivo completo con este contenido exacto: + +```tsx +import { useEffect, useState } from 'react' +import { Check } from 'lucide-react' +import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetFooter } from '@/components/ui/sheet' +import { Label } from '@/components/ui/label' +import { Input } from '@/components/ui/input' +import { Button } from '@/components/ui/button' +import { SUPABASE_URL, SUPABASE_ANON_KEY } from '@/lib/supabase' +import { useAuth } from '@/auth/AuthContext' + +// Lee el access token directamente de localStorage sin pasar por el cliente Supabase. +// Evita contención con processLock — el save de perfil no necesita refresh de token +// porque es una operación rápida y el token está fresco en sesiones activas. +function getAccessToken(): string | null { + try { + const projectRef = new URL(SUPABASE_URL).hostname.split('.')[0] + const raw = localStorage.getItem(`sb-${projectRef}-auth-token`) + if (!raw) return null + const parsed = JSON.parse(raw) as { access_token?: string } + return parsed.access_token ?? null + } catch { + return null + } +} + +interface ProfileSheetProps { + open: boolean + onOpenChange: (open: boolean) => void +} + +export function ProfileSheet({ open, onOpenChange }: ProfileSheetProps) { + const { user, updateUser } = useAuth() + const [name, setName] = useState('') + const [company, setCompany] = useState('') + + useEffect(() => { + if (open && user) { + setName(user.name || user.email || '') + setCompany(user.company ?? '') + } + // Intencional: solo inicializar cuando el sheet se abre, no en cada cambio de `user` + // (evita resetear el formulario mientras el usuario está editando). + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [open]) + + function handleSave() { + if (!user || !name.trim()) return + + const savedName = name.trim() + const savedCompany = company.trim() || undefined + + // Actualización optimista: aplicar cambios localmente y cerrar el Sheet de inmediato. + // El Sheet nunca queda abierto esperando la respuesta del servidor. + // La modal de Radix bloquea todo el UI mientras está abierta, así que cerramos + // ANTES del fetch para que el usuario pueda seguir trabajando sin demora. + updateUser({ name: savedName, company: savedCompany }) + onOpenChange(false) + + // Persistir en Supabase en background — fire & forget. + // Usamos raw fetch (no el cliente Supabase) para evitar contención con processLock. + // getSession() interno de supabase.from().update() adquiere processLock y bloquea + // otras queries de la página mientras espera. Raw fetch lee el token de localStorage + // directamente — sin lock, sin overhead de auth. + const token = getAccessToken() + if (!token) return + + void fetch( + `${SUPABASE_URL}/rest/v1/users?id=eq.${encodeURIComponent(user.id)}`, + { + method: 'PATCH', + headers: { + apikey: SUPABASE_ANON_KEY, + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + Prefer: 'return=minimal', + }, + body: JSON.stringify({ name: savedName, company: savedCompany ?? null }), + signal: AbortSignal.timeout(10000), + } + ) + .then((res) => { + if (!res.ok) console.error('[ProfileSheet] Error guardando perfil:', res.status) + }) + .catch((err) => { + console.error('[ProfileSheet] Error guardando perfil:', err) + }) + } + + return ( + + + + Editar perfil + + +
+
+ + setName(e.target.value)} + placeholder="Tu nombre" + /> +
+ +
+ + setCompany(e.target.value)} + placeholder="InQuality" + /> +
+ +
+ + +
+
+ + + + + +
+
+ ) +} +``` + +--- + +## Orden de ejecución + +1. Leer `src/components/ProfileSheet.tsx` para confirmar estado actual +2. Reemplazar con el contenido exacto de arriba +3. `npm run build` — debe dar 0 errores TypeScript +4. `npm run test` — todos los Vitest verdes +5. `git add src/components/ProfileSheet.tsx` +6. `git commit -m "fix(sprint-5/etapa-4g): revert ProfileSheet a raw fetch — evita processLock contention"` +7. `git push origin main` + +--- + +## Lo que NO entra + +- NO tocar ningún otro archivo +- NO agregar logs de debug +- NO cambios en supabase.ts, AuthContext, stores, ni ningún otro módulo + +--- + +## Criterios de validación + +- [ ] `npm run build` limpio (0 errores TypeScript) +- [ ] `npm run test` — Vitest verdes +- [ ] `git push origin main` ejecutado +- [ ] **Validación visual del director antes del OK formal** + +Validación funcional (post-deploy, a cargo del director): +- [ ] Abrir ProfileSheet → editar empresa → Guardar → Sheet cierra inmediatamente +- [ ] UI responde inmediatamente después de cerrar (todos los botones funcionan) +- [ ] Refrescar página → cambio persiste