Etapas 3-6 del Sprint 2. Resumen de cambios:
**Reporte web (Etapa 3)**
- ActivitiesTable: filas expandibles con desglose fijo + recursos por actividad
- CumulativeSavingsChart: fix payback label superpuesto con eje Y
- BpmnCanvas: badge ⚡ 14→20px
- TECH_DEBT cerrado: chart -inversión (ya estaba), payback label, badge
**Workspace (Etapa 4)**
- WorkspacePage: edición inline de nombre de proceso y cliente
(click → input → Enter/blur guarda en IndexedDB, checkmark naranja 1.5s)
**PDF (Etapas 5-6)**
- pdf-sections: drawResourcesSection — tabla condicional de recursos por actividad
con chips de tipo (rol/persona/sistema/equipamiento/insumo)
- pdf-export: página de recursos (pág. 4 cuando hay datos, metodología pasa a pág. 5)
- Fix truncamiento: splitTextToSize en resourceName y top3 ROI
- Fix texto: "ejecución" con tilde, header "Esp. (USD)" sin truncar
- TECH_DEBT cerrado: truncamiento notas metodológicas
**Tests**
- 7 tests nuevos: drawResourcesSection (4) + comportamiento condicional PDF (3)
- Total: 541 → 548 tests verdes
BREAKING: exportScenarioPdf ahora recibe activities[] como parámetro adicional.
8.6 KiB
Sprint 2 — Etapa 4: Edición inline de nombre de proceso y cliente
Tipo: UI — un solo componente. Sin cambios en dominio, persistencia ni reporte. Objetivo: hacer editable el nombre del proceso y el cliente directamente desde el header del workspace. Hoy son campos estáticos (
<p>); deben convertirse en inputs inline que guardan en IndexedDB al confirmar. Resultado esperado: click sobre nombre o cliente → input inline → Enter o blur guarda → checkmark naranja breve confirma. Validación visual del director requerida antes del OK formal.
Archivo a modificar
Un único archivo: src/features/workspace/WorkspacePage.tsx
Si durante la implementación identificás que otro archivo necesita cambio, detenete y reportá el conflicto antes de tocarlo. No procedas sin confirmación.
Contexto: estado actual del header
El header del workspace (líneas ~139–144) tiene hoy:
<div className="flex-1 min-w-0">
<p className="text-sm font-semibold text-slate-800 truncate">{currentProcess.name}</p>
{currentProcess.clientName && (
<p className="text-xs text-slate-400 truncate">{currentProcess.clientName}</p>
)}
</div>
Ambos son texto estático. La función updateProcess ya existe en el store
(useProcessStore) y persiste en IndexedDB — no requiere ningún cambio fuera de este archivo.
Cambios requeridos
1. Agregar updateProcess al destructuring del store
// ANTES (línea ~26)
const { currentProcess, activities, isLoading, error, loadProcess, gateways } = useProcessStore()
// DESPUÉS
const { currentProcess, activities, isLoading, error, loadProcess, gateways, updateProcess } = useProcessStore()
2. Agregar iconos al import de lucide-react
Agregar Check y Pencil al import existente:
import {
Loader2, BarChart3, Play, AlertTriangle,
PanelRightClose, PanelRightOpen, Home, GitMerge, MousePointer2,
Check, Pencil
} from 'lucide-react'
3. Agregar estado local para edición inline
Insertar antes del return, junto a los otros estados del componente:
// Estado para edición inline del header
const [editingField, setEditingField] = useState<'name' | 'client' | null>(null)
const [editValue, setEditValue] = useState('')
const [savedField, setSavedField] = useState<'name' | 'client' | null>(null)
const savedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
useRef ya está importado desde React — no requiere cambio en imports de React.
4. Agregar función saveField
Insertar junto a las otras funciones del componente (cerca de handleSimulate):
async function saveField(field: 'name' | 'client') {
if (field === 'name') {
const trimmed = editValue.trim()
if (trimmed && trimmed !== currentProcess?.name) {
await updateProcess({ name: trimmed })
}
} else {
const trimmed = editValue.trim()
if (trimmed !== (currentProcess?.clientName ?? '')) {
await updateProcess({ clientName: trimmed })
}
}
setEditingField(null)
// Wow: checkmark naranja breve
setSavedField(field)
if (savedTimerRef.current) clearTimeout(savedTimerRef.current)
savedTimerRef.current = setTimeout(() => setSavedField(null), 1500)
}
5. Reemplazar el bloque estático del header
Reemplazar las líneas ~139–144 (el <div className="flex-1 min-w-0"> completo) con:
<div className="flex-1 min-w-0">
{/* Nombre del proceso — editable inline */}
{editingField === 'name' ? (
<input
autoFocus
className="text-sm font-semibold text-slate-800 bg-transparent border-b border-[#F59845] outline-none w-full"
value={editValue}
onChange={(e) => setEditValue(e.target.value)}
onBlur={() => saveField('name')}
onKeyDown={(e) => {
if (e.key === 'Enter') { e.preventDefault(); saveField('name') }
if (e.key === 'Escape') setEditingField(null)
}}
/>
) : (
<div
className="flex items-center gap-1 group cursor-text"
onClick={() => { setEditValue(currentProcess.name); setEditingField('name') }}
>
<p className="text-sm font-semibold text-slate-800 truncate">{currentProcess.name}</p>
{savedField === 'name'
? <Check className="h-3.5 w-3.5 text-[#F59845] shrink-0" />
: <Pencil className="h-3 w-3 text-slate-300 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity" />
}
</div>
)}
{/* Cliente — editable inline */}
{editingField === 'client' ? (
<input
autoFocus
className="text-xs text-slate-400 bg-transparent border-b border-[#F59845] outline-none w-full"
value={editValue}
onChange={(e) => setEditValue(e.target.value)}
onBlur={() => saveField('client')}
onKeyDown={(e) => {
if (e.key === 'Enter') { e.preventDefault(); saveField('client') }
if (e.key === 'Escape') setEditingField(null)
}}
placeholder="Agregar cliente…"
/>
) : (
<div
className="flex items-center gap-1 group cursor-text"
onClick={() => { setEditValue(currentProcess.clientName ?? ''); setEditingField('client') }}
>
{currentProcess.clientName
? <p className="text-xs text-slate-400 truncate">{currentProcess.clientName}</p>
: <p className="text-xs text-slate-300 truncate italic">Agregar cliente…</p>
}
{savedField === 'client'
? <Check className="h-3 w-3 text-[#F59845] shrink-0" />
: <Pencil className="h-2.5 w-2.5 text-slate-300 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity" />
}
</div>
)}
</div>
Comportamientos esperados
- Click sobre el nombre → input inline con el valor actual, borde inferior naranja
- Enter o blur → guarda, vuelve a modo lectura, aparece ✓ naranja 1.5 segundos
- Escape → cancela sin guardar, vuelve al valor anterior
- Nombre vacío al guardar → no guarda (el
if (trimmed && ...)lo previene), vuelve al valor anterior - Cliente vacío al guardar → guarda string vacío (limpiar el cliente es una acción válida)
- Sin cliente asignado → muestra "Agregar cliente…" en gris itálico; al hacer click se abre el input vacío
- El ícono ✏️ es invisible hasta hacer hover sobre la fila — indica que es editable sin ocupar espacio visualmente
Verificaciones obligatorias antes de reportar
1. Verificación de scope con git diff — OBLIGATORIO
git diff --name-only HEAD
El resultado debe contener únicamente:
src/features/workspace/WorkspacePage.tsx
Si aparece cualquier archivo no listado:
- Revertilo:
git checkout HEAD -- <archivo> - Listarlo bajo "Conflictos de scope" en el reporte
- No incluirlo en el entregable
2. Build limpio
npm run build
Sin errores TypeScript.
3. Tests
npm run test
Todos los tests existentes deben seguir verdes. Esta etapa no agrega tests nuevos (UI pura, sin lógica de dominio nueva).
4. Smoke test manual
- Click sobre el nombre del proceso → se abre input con valor actual
- Tipear un nombre nuevo → Enter → se cierra, aparece ✓ naranja breve, el nombre cambió
- Recargar la página → el nombre nuevo persiste (IndexedDB)
- Click sobre el cliente → input editable
- Borrar el cliente completamente → guardar → el campo queda vacío mostrando "Agregar cliente…"
- Escape cancela sin guardar
Alcance estricto — lo que NO entra en esta etapa
- ❌
GlobalSettingsPanel.tsx— no tocar (edición desde settings sigue funcionando igual) - ❌ Ningún archivo de reporte (
src/features/report/) — Etapa 3 ya cerrada - ❌ Ningún archivo de dominio (
src/domain/) — Etapas 1–3 ya cerraron dominio - ❌ Ningún archivo de persistencia (
src/persistence/) —updateProcessdel store es suficiente - ❌ PDFs — Etapa 5
- ❌ Edición de campos adicionales del proceso (frecuencia anual, overhead) — BACKLOG
Reporte esperado al terminar
## Etapa 4 completada
### Archivos modificados
- src/features/workspace/WorkspacePage.tsx
### Cambios realizados
- Edición inline de nombre de proceso: click → input → Enter/blur guarda
- Edición inline de cliente: click → input → Enter/blur guarda
- Checkmark naranja (#F59845) confirmatorio con timeout 1.5s
- Ícono ✏️ visible al hover, oculto en reposo
- Escape cancela sin guardar en ambos campos
### Verificaciones
- git diff --name-only HEAD: solo WorkspacePage.tsx ✅
- npm run build: ✅
- npm run test: X/X tests verdes ✅
- Smoke test: [descripción breve]
### Conflictos de scope
[ninguno / lista de archivos revertidos y razón]
### Notas para Etapas siguientes
[hallazgos relevantes para Etapas 5+]