feat(sprint-1-5): cierre Etapa 2 - formatPayback + fixes de marca
[
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
GO — Etapa 2: Helper formatPayback + aplicación
|
||||
|
||||
Tu plan original tenía 7 it.todo listos. Esta etapa los convierte en
|
||||
tests reales, implementa el helper y lo aplica en 3 lugares.
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
PRE-TAREAS (actualizar artefactos de Etapa 1)
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
Antes de implementar formatPayback, hacé estas 3 actualizaciones rápidas:
|
||||
|
||||
1. Actualizar etapa-1-audit.md — fila de GatewayPanel.tsx:
|
||||
Cambiar la categoría de las 5 referencias blue-* de GatewayPanel de
|
||||
"Mantener azul funcional / Evaluar en Sprint 2" a:
|
||||
|
||||
"Decisión arquitectónica: mantener azul informativo. No es color
|
||||
de marca, es semántica funcional (info banner, valor inmutable,
|
||||
patrón Bootstrap/Tailwind estándar). NO migrar."
|
||||
|
||||
2. Actualizar etapa-1-audit.md — agregar fila al final de la sección
|
||||
"Hallazgos — Hex hardcodes" para el donut chart:
|
||||
|
||||
| chart-options.ts:123 | #3b82f6 → naranja | Composición directo/overhead
|
||||
| en donut. Decisión visual: el sector "directo" va en naranja InQ
|
||||
| #F59845. El sector "overhead" NO debe ser otro naranja (pierde
|
||||
| contraste) — usar slate-400 (#94A3B8) o naranja muy claro
|
||||
| (#FED7AA). Decisión final en Etapa 3 mirando el render. |
|
||||
|
||||
3. Verificar contraste WCAG de #F59845 sobre blanco. Ejecutá:
|
||||
|
||||
node -e "
|
||||
const hex = '#F59845';
|
||||
const r = parseInt(hex.slice(1,3), 16) / 255;
|
||||
const g = parseInt(hex.slice(3,5), 16) / 255;
|
||||
const b = parseInt(hex.slice(5,7), 16) / 255;
|
||||
const lin = (c) => c <= 0.03928 ? c/12.92 : Math.pow((c+0.055)/1.055, 2.4);
|
||||
const L1 = 0.2126*lin(r) + 0.7152*lin(g) + 0.0722*lin(b);
|
||||
const L2 = 1;
|
||||
const ratio = (L2 + 0.05) / (L1 + 0.05);
|
||||
console.log('Contrast ratio:', ratio.toFixed(2), ':1');
|
||||
console.log('AA normal text (4.5):', ratio >= 4.5 ? 'PASS' : 'FAIL');
|
||||
console.log('AA large text (3.0):', ratio >= 3.0 ? 'PASS' : 'FAIL');
|
||||
"
|
||||
|
||||
Anotá el resultado en etapa-1-audit.md como nueva sección
|
||||
"Accesibilidad — Contraste de marca". Si el resultado es FAIL para
|
||||
AA normal, agregá las 4 opciones de remediación al audit:
|
||||
|
||||
A) Aceptar contraste sub-WCAG en CTAs (riesgo accesibilidad)
|
||||
B) Oscurecer naranja a #D97706 para CTAs (rompe marca parcialmente)
|
||||
C) Usar texto oscuro #451A03 en CTAs sobre naranja (cambia look)
|
||||
D) Forzar font-weight: 700 en CTAs primarios (cumple AA large)
|
||||
|
||||
Marcá la opción D como propuesta a implementar en Etapa 3 cuando
|
||||
rediseñemos KPI cards y CTAs. NO modificar componentes en esta etapa.
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
DECISIONES YA TOMADAS (no consultar de nuevo)
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
Confirmadas con el usuario:
|
||||
|
||||
Payback negativo → "Sin payback (costo aumenta)"
|
||||
(caso donde costo automatizado > costo actual, escenario
|
||||
matemáticamente posible pero indica error de configuración o
|
||||
automatización inviable)
|
||||
|
||||
Texto del formato → usar caracteres compactos "<" y "~"
|
||||
(universales y legibles en KPI cards)
|
||||
|
||||
Pluralización → siempre plural incluso con decimales
|
||||
("1.0 meses", no "1.0 mes")
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
TAREA PRINCIPAL — IMPLEMENTAR formatPayback
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
1. Implementar formatPayback en src/lib/format.ts:
|
||||
|
||||
function formatPayback(months: number): string {
|
||||
if (Number.isNaN(months) || !isFinite(months)) {
|
||||
return "No recupera en horizonte";
|
||||
}
|
||||
if (months < 0) {
|
||||
return "Sin payback (costo aumenta)";
|
||||
}
|
||||
if (months === 0) {
|
||||
return "Inmediato";
|
||||
}
|
||||
if (months < 0.1) {
|
||||
return "< 1 día";
|
||||
}
|
||||
if (months < 1) {
|
||||
return `< 1 mes (${months.toFixed(2)} meses)`;
|
||||
}
|
||||
if (months < 12) {
|
||||
return `${months.toFixed(1)} meses`;
|
||||
}
|
||||
if (months < 24) {
|
||||
const years = (months / 12).toFixed(1);
|
||||
return `${months.toFixed(1)} meses (~${years} años)`;
|
||||
}
|
||||
return `${(months / 12).toFixed(1)} años`;
|
||||
}
|
||||
|
||||
2. Convertir los 7 it.todo existentes en tests reales:
|
||||
|
||||
- formatPayback(0) === "Inmediato"
|
||||
- formatPayback(0.05) === "< 1 día"
|
||||
- formatPayback(0.5) === "< 1 mes (0.50 meses)"
|
||||
- formatPayback(6) === "6.0 meses"
|
||||
- formatPayback(15) === "15.0 meses (~1.3 años)"
|
||||
- formatPayback(30) === "2.5 años"
|
||||
- formatPayback(Infinity) === "No recupera en horizonte"
|
||||
|
||||
3. Agregar 5 tests de edge cases:
|
||||
|
||||
- formatPayback(-1) === "Sin payback (costo aumenta)"
|
||||
// payback negativo: costo automatizado supera al actual
|
||||
- formatPayback(-100) === "Sin payback (costo aumenta)"
|
||||
// negativo grande: mismo comportamiento
|
||||
- formatPayback(NaN) === "No recupera en horizonte"
|
||||
// NaN cae en el primer guard
|
||||
- formatPayback(11.95) === "11.9 meses"
|
||||
// verificar que el borde con 12 no salta a "1.0 años" prematuramente
|
||||
- formatPayback(12.0) === "12.0 meses (~1.0 años)"
|
||||
// exactamente en el umbral
|
||||
|
||||
Total: 12 tests verdes en tests/lib/format-payback.test.ts
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
APLICACIÓN EN 3 LUGARES
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
a) RoiKpiBar.tsx (web app):
|
||||
|
||||
- Reemplazar el formato actual del KPI "PAYBACK" por formatPayback(months)
|
||||
- El KPI card debe mostrar el string completo
|
||||
- PROBLEMA POTENCIAL: strings largos como "15.0 meses (~1.3 años)"
|
||||
pueden desbordar la card. Soluciones:
|
||||
- Opción 1: reducir font-size del valor a 14px en el card de payback
|
||||
(solo en ese card, no en los otros KPIs)
|
||||
- Opción 2: usar 2 líneas — primera línea cifra principal, segunda
|
||||
línea texto secundario en font-size menor
|
||||
|
||||
PROBÁ ambas y mostrame screenshot de cada una en Etapa 3. Por ahora
|
||||
implementá la opción 1 (más simple, mantiene grilla consistente).
|
||||
|
||||
b) src/lib/export/pdf-sections-roi.ts → drawRoiKpiCards:
|
||||
|
||||
- Aplicar formatPayback en la KPI card del PDF
|
||||
- Verificar ancho: el card actual mide ~120px en página A4. String
|
||||
"15.0 meses (~1.3 años)" en font 16px puede no entrar.
|
||||
Si es así, reducir font-size de esa card específica a 14px.
|
||||
- Probar con paybacks de distintos rangos: 0.5, 6, 15, 30 meses
|
||||
para ver el comportamiento.
|
||||
|
||||
c) src/lib/export/csv-export.ts:
|
||||
|
||||
- En la línea de metadata "# Payback (meses): X.XX" reemplazar el
|
||||
número crudo por formatPayback formateado
|
||||
- AGREGAR una segunda línea inmediatamente después:
|
||||
"# Payback (meses, raw): X.XXXX"
|
||||
Esto preserva compatibilidad con integraciones futuras de BI/Excel
|
||||
que necesiten el valor numérico crudo.
|
||||
|
||||
d) BONUS — verificar TransparencySection:
|
||||
|
||||
El detector de ROI inusual (Etapa 3 de Sprint 1) menciona payback en
|
||||
alguno de sus 4 casos. Verificá si el texto incluye payback formateado
|
||||
o crudo. Si es crudo, aplicar formatPayback también ahí.
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
ENTREGABLES
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
[ ] etapa-1-audit.md actualizado con 3 correcciones (GatewayPanel,
|
||||
donut chart, contraste WCAG)
|
||||
[ ] formatPayback implementado en src/lib/format.ts
|
||||
[ ] 12 tests verdes en tests/lib/format-payback.test.ts
|
||||
[ ] formatPayback aplicado en RoiKpiBar.tsx
|
||||
[ ] formatPayback aplicado en pdf-sections-roi.ts (drawRoiKpiCards)
|
||||
[ ] formatPayback aplicado en csv-export.ts (con doble línea: formateado
|
||||
+ raw para compatibilidad)
|
||||
[ ] TransparencySection verificado y actualizado si aplica
|
||||
[ ] 443 tests previos + 12 nuevos = 455+ tests verdes
|
||||
[ ] npm run build limpio
|
||||
[ ] Screenshot del RoiKpiBar mostrando un payback de 10.6 meses
|
||||
(caso del PDF roi-realistic)
|
||||
[ ] PDF de muestra generado con setup realista del brief:
|
||||
- medium-with-gateways
|
||||
- 3 automatizables, $200/$20 costos
|
||||
- 5000 ejecuciones/año, 3 años, USD 80.000 inversión
|
||||
- Resultado esperado: payback ~10.6 meses (NO "0.0 meses")
|
||||
→ guardar como tests/e2e/__output__/payback-formatted-sample.pdf
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
REPORTE FINAL A PRESENTAR
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
1. Resultado del cálculo de contraste WCAG (PASS/FAIL para AA normal
|
||||
y AA large)
|
||||
2. Confirmación de las 3 actualizaciones del audit
|
||||
3. Tabla de tests con resultados (12 verdes esperados)
|
||||
4. Screenshot del RoiKpiBar con payback formateado
|
||||
5. PDF de muestra presentado con present_files para que el usuario
|
||||
abra en Preview y valide visualmente
|
||||
6. Cantidad final de tests verdes
|
||||
7. Cualquier desbordamiento de UI/PDF detectado y cómo se resolvió
|
||||
|
||||
NO arranques Etapa 3 sin mi OK explícito.
|
||||
|
||||
Go.
|
||||
Reference in New Issue
Block a user