From f4d46080a67aead33f427d5fc1c79a9559d9938a Mon Sep 17 00:00:00 2001 From: Alexis Date: Thu, 28 May 2026 12:37:07 +0200 Subject: [PATCH 1/3] Enhance Login and Register components with new UI elements and improved layout. Added demo visualization in Login and refined registration flow in Register, including error handling and user guidance. --- frontend/src/pages/Login.jsx | 251 +++++++++++++++++++++++++++++--- frontend/src/pages/Register.jsx | 80 ++++++---- 2 files changed, 277 insertions(+), 54 deletions(-) diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx index 0afb43b..c91df6a 100644 --- a/frontend/src/pages/Login.jsx +++ b/frontend/src/pages/Login.jsx @@ -3,7 +3,195 @@ import { Link, useNavigate, useSearchParams } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; import { authService } from '../services/authService'; import { API_BASE_URL } from '../config'; -import { FiEye, FiEyeOff } from 'react-icons/fi'; +import { FiArrowLeft, FiEye, FiEyeOff } from 'react-icons/fi'; +import { ComposedChart, Line, XAxis, YAxis, CartesianGrid, ReferenceArea, ReferenceLine, ResponsiveContainer } from 'recharts'; + +const DEMO_TERMS = [ + { name: 'Muy Bajo', xVal: 0.10, mf: { supportStart: 0.00, coreStart: 0.00, coreEnd: 0.10, supportEnd: 0.22 } }, + { name: 'Bajo', xVal: 0.28, mf: { supportStart: 0.12, coreStart: 0.22, coreEnd: 0.33, supportEnd: 0.44 } }, + { name: 'Medio', xVal: 0.50, mf: { supportStart: 0.35, coreStart: 0.44, coreEnd: 0.56, supportEnd: 0.65 } }, + { name: 'Alto', xVal: 0.72, mf: { supportStart: 0.56, coreStart: 0.67, coreEnd: 0.78, supportEnd: 0.88 } }, + { name: 'Muy Alto', xVal: 0.90, mf: { supportStart: 0.78, coreStart: 0.90, coreEnd: 1.00, supportEnd: 1.00 } }, +]; +const DEMO_COLORS = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6', '#d946ef']; + +function FakeDemoPanel() { + const [visibleCount, setVisibleCount] = useState(0); + const [fading, setFading] = useState(false); + + useEffect(() => { + let timeout; + if (visibleCount < DEMO_TERMS.length) { + timeout = setTimeout(() => setVisibleCount(v => v + 1), 1300); + } else { + timeout = setTimeout(() => { + setFading(true); + setTimeout(() => { + setVisibleCount(0); + setFading(false); + }, 500); + }, 2400); + } + return () => clearTimeout(timeout); + }, [visibleCount]); + + const visibleTerms = DEMO_TERMS.slice(0, visibleCount); + const activeIndex = visibleCount - 1; + + return ( +
+ {/* Mini header badge */} +
+ Paso 2 + · + Modelar Conceptos Difusos + + + En vivo + +
+ + {/* Term pills */} +
+ {DEMO_TERMS.map((term, index) => { + const color = DEMO_COLORS[index % DEMO_COLORS.length]; + const isVisible = index < visibleCount; + const isActive = index === activeIndex; + return ( + + {term.name} + + ); + })} +
+ + {/* Chart */} +
+ + + + + + {visibleTerms.map((term, index) => { + const color = DEMO_COLORS[index % DEMO_COLORS.length]; + const isActive = index === activeIndex; + return ( + + ); + })} + {visibleTerms.map((term, index) => { + const color = DEMO_COLORS[index % DEMO_COLORS.length]; + const isActive = index === activeIndex; + return ( + + ); + })} + {visibleTerms.map((term, index) => { + const color = DEMO_COLORS[index % DEMO_COLORS.length]; + const isActive = index === activeIndex; + const trapezeData = [ + { x: term.mf.supportStart, y: 0 }, + { x: term.mf.coreStart, y: 1 }, + { x: term.mf.coreEnd, y: 1 }, + { x: term.mf.supportEnd, y: 0 }, + ]; + return ( + + ); + })} + + +
+ + {/* Terms table */} +
+
+ Términos modelados + {visibleCount} / {DEMO_TERMS.length} +
+
+ {DEMO_TERMS.map((term, index) => { + const isVisible = index < visibleCount; + const isActive = index === activeIndex; + const color = DEMO_COLORS[index % DEMO_COLORS.length]; + return ( +
+ + + {term.name} + + {isVisible && ( + + [{term.mf.coreStart.toFixed(2)}, {term.mf.coreEnd.toFixed(2)}] + + )} + {isActive && ( + + )} +
+ ); + })} +
+
+
+ ); +} export default function Login() { const [email, setEmail] = useState(''); @@ -71,21 +259,37 @@ export default function Login() { }; return ( -
-
- -
-

Deck of Cards

-

Accede a tu historial y gráficas guardadas

+
+
+
+

Deck of Cards

+

Modela y compara de forma visual

+

+ Construye funciones de pertenencia difusa, guarda tu historial y vuelve a trabajar donde lo dejaste. +

+ + + Ir al editor principal + +
- {error && ( -
- {error} +
+
+

Deck of Cards

+

Accede a tu historial y gráficas guardadas

- )} -
+ {error && ( +
+ {error} +
+ )} + +
Entrar - + -
-
-
O
+
+
+
O
+
+ + + +

¿Nuevo por aquí? Crea una cuenta

- - - -

¿Nuevo por aquí? Crea una cuenta

); diff --git a/frontend/src/pages/Register.jsx b/frontend/src/pages/Register.jsx index 02881ff..09174f4 100644 --- a/frontend/src/pages/Register.jsx +++ b/frontend/src/pages/Register.jsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { useNavigate, Link } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; import { authService } from '../services/authService'; -import { FiEye, FiEyeOff } from 'react-icons/fi'; +import { FiArrowLeft, FiEye, FiEyeOff } from 'react-icons/fi'; export default function Register() { const [username, setUsername] = useState(''); @@ -95,34 +95,51 @@ export default function Register() { }; return ( -
-
- -
-

- {verificationRequired ? 'Verifica tu email' : 'Crear Cuenta'} -

-

- {verificationRequired - ? `Introduce el código enviado a ${pendingEmail}` - : 'Inicia sesión para guardar tu progreso'} +

+
+
+

Deck of Cards

+

+ Crea tu cuenta y guarda cada modelo +

+

+ Registra tus criterios, conserva resultados en el historial y retoma tus análisis cuando quieras.

+ + + Ir al editor principal +
- {error && ( -
- {error} +
+
+

+ {verificationRequired ? 'Verifica tu email' : 'Crear Cuenta'} +

+

+ {verificationRequired + ? `Introduce el código enviado a ${pendingEmail}` + : 'Inicia sesión para guardar tu progreso'} +

- )} - {infoMessage && ( -
- {infoMessage} -
- )} + {error && ( +
+ {error} +
+ )} - {!verificationRequired ? ( -
+ {infoMessage && ( +
+ {infoMessage} +
+ )} + + {!verificationRequired ? ( +
{isSubmitting ? 'Enviando código...' : 'Registrarse'} - - ) : ( -
+
+ ) : ( +
{isResending ? 'Reenviando...' : 'Reenviar código'} - - )} + + )} -

- ¿Ya tienes cuenta? Inicia sesión aquí -

+

+ ¿Ya tienes cuenta? Inicia sesión aquí +

+
); From c8077e57efbee5a25a0ee59302d712d1ed56f60f Mon Sep 17 00:00:00 2001 From: Alexis Date: Fri, 29 May 2026 10:38:34 +0200 Subject: [PATCH 2/3] Refactor Login and Register components to streamline UI and enhance user experience. Removed unused demo visualization from Login and integrated AuthDemoPanel into Register for improved user guidance. --- frontend/src/components/AuthDemoPanel.jsx | 407 ++++++++++++++++++++++ frontend/src/pages/Login.jsx | 261 ++------------ frontend/src/pages/Register.jsx | 8 +- 3 files changed, 450 insertions(+), 226 deletions(-) create mode 100644 frontend/src/components/AuthDemoPanel.jsx diff --git a/frontend/src/components/AuthDemoPanel.jsx b/frontend/src/components/AuthDemoPanel.jsx new file mode 100644 index 0000000..05efddd --- /dev/null +++ b/frontend/src/components/AuthDemoPanel.jsx @@ -0,0 +1,407 @@ +import React, { useState, useEffect, useRef, useCallback } from 'react'; +import { + ComposedChart, Area, Line, + XAxis, YAxis, CartesianGrid, + ReferenceArea, ReferenceLine, + ResponsiveContainer, +} from 'recharts'; + +// ── Fake demo data ────────────────────────────────────────────────────────── + +const STEP1_CARDS = ['Bajo', 'Medio', 'Alto', 'Perfecto']; +const STEP1_BLANKS = [3, 1, 4]; // huecos asimétricos entre cartas + +const STEP2_TERMS = [ + { name: 'Bajo', xVal: 0.08, mf: { supportStart: 0.00, coreStart: 0.00, coreEnd: 0.06, supportEnd: 0.30 } }, + { name: 'Medio', xVal: 0.38, mf: { supportStart: 0.18, coreStart: 0.38, coreEnd: 0.38, supportEnd: 0.59 } }, // pico/triángulo + { name: 'Alto', xVal: 0.65, mf: { supportStart: 0.52, coreStart: 0.60, coreEnd: 0.69, supportEnd: 0.77 } }, + { name: 'Perfecto', xVal: 0.92, mf: { supportStart: 0.74, coreStart: 0.88, coreEnd: 1.00, supportEnd: 1.00 } }, +]; +const STEP2_COLORS = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6']; + +// Igual que interpolateY en useGraphData.js: +// · Zona buffer justo fuera del soporte → 0 (ancla la línea en y=0) +// · Más allá del buffer → null (corte, sin línea horizontal) +const TRAP_BUF = 1.1e-4; +function trapVal(x, s0, c0, c1, s1) { + if (x < s0 - TRAP_BUF || x > s1 + TRAP_BUF) return null; + if (x < s0 || x > s1) return 0; + if (x >= c0 && x <= c1) return 1; + if (x < c0 && c0 > s0) return (x - s0) / (c0 - s0); + if (x > c1 && s1 > c1) return (s1 - x) / (s1 - c1); + return 0; +} + +// Solo 'Alto' es IT2 (tiene subescala). LMF es triángulo → banda "lente" visible. +const STEP3_TERMS = [ + { name: 'Bajo', color: '#ef4444', type: 't1', pts: [0.00, 0.00, 0.06, 0.30] }, + { name: 'Medio', color: '#f59e0b', type: 't1', pts: [0.18, 0.38, 0.38, 0.59] }, + { name: 'Alto', color: '#10b981', type: 't2', u: [0.49, 0.57, 0.71, 0.81], l: [0.56, 0.64, 0.64, 0.76] }, + { name: 'Perfecto', color: '#3b82f6', type: 't1', pts: [0.74, 0.88, 1.00, 1.00] }, +]; + +// Puntos clave del trapecio (piezas lineales → basta con vértices, como en el paso 2). +// Recharts interpola el trazo entre ellos con animación fluida. +function getTermLineData(term) { + const name = term.name; + + if (term.type === 't1') { + const [s0, c0, c1, s1] = term.pts; + const xs = new Set([s0, c0, c1, s1]); + if (s0 <= 0.001) xs.add(s0 - TRAP_BUF); + if (s1 >= 0.999) xs.add(s1 + TRAP_BUF); + return Array.from(xs).sort((a, b) => a - b).map(x => ({ + x, + [name]: trapVal(x, s0, c0, c1, s1), + })); + } + + const xs = new Set([...term.u, ...term.l]); + return Array.from(xs).sort((a, b) => a - b).map(x => { + const upper = trapVal(x, ...term.u); + const lower = trapVal(x, ...term.l); + return { + x, + [`${name}_upper`]: upper, + [`${name}_lower`]: lower, + [`${name}_range`]: (lower === null && upper === null) + ? null + : [lower ?? 0, upper ?? 0], + }; + }); +} + +const STEP_LABELS = [ + { n: 1, label: 'Escala' }, + { n: 2, label: 'Modelado' }, + { n: 3, label: 'Espectro IT2' }, +]; + +// ── Step sub-components ───────────────────────────────────────────────────── + +function Step1Content({ count }) { + const done = count >= STEP1_CARDS.length; + return ( +
+

Criterio: Calidad Investigadora

+
+ {STEP1_CARDS.map((name, i) => ( + + {i > 0 && ( +
+
+ + ×{STEP1_BLANKS[i - 1]} + +
+ )} +
+
+ {i + 1} + {i + 1} + {name} +
+
+ + ))} +
+
+ ✓ {STEP1_CARDS.length} niveles definidos +
+
+
+
+
+ ); +} + +function Step2Content({ count }) { + const visibleTerms = STEP2_TERMS.slice(0, count); + const activeIndex = count - 1; + const showSubscale = count >= 3; + + return ( +
+
+ {STEP2_TERMS.map((term, i) => { + const color = STEP2_COLORS[i % STEP2_COLORS.length]; + const isVisible = i < count; + const isActive = i === activeIndex; + return ( + + {term.name} + {i === 2 && showSubscale && ( + IT2 + )} + + ); + })} +
+ +
+ + + + + + {visibleTerms.map((term, i) => { + const color = STEP2_COLORS[i % STEP2_COLORS.length]; + const isActive = i === activeIndex; + return ( + + ); + })} + {visibleTerms.map((term, i) => { + const color = STEP2_COLORS[i % STEP2_COLORS.length]; + const isActive = i === activeIndex; + return ( + + ); + })} + {visibleTerms.map((term, i) => { + const color = STEP2_COLORS[i % STEP2_COLORS.length]; + const isActive = i === activeIndex; + const trapezeData = [ + { x: term.mf.supportStart, y: 0 }, + { x: term.mf.coreStart, y: 1 }, + { x: term.mf.coreEnd, y: 1 }, + { x: term.mf.supportEnd, y: 0 }, + ]; + return ( + + ); + })} + + +
+ + {/* Mini SubscaleModal inline */} +
+
+ Diseñar Subescala + · + Alto + · + Pendiente Descendente + +
+
+
+ 1 +
+
+
+
+ MÍN + 2 +
+ +
+ MÁX + 5 +
+
+ ¿Dudas? Rango ✓ +
+
+ 2 +
+
+
+ CARTAS + 3 +
+ Distancia exacta +
+
+ 3 +
+
+
+
+ ); +} + +function Step3Content({ count }) { + return ( +
+

Espectro difuso · Calidad Investigadora

+
+ + + + + + {STEP3_TERMS.slice(0, count).map((term, i) => { + const isNewest = i === count - 1; + const data = getTermLineData(term); + return ( + + {term.type === 't1' ? ( + + ) : ( + <> + + + + + )} + + ); + })} + + +
+ {STEP3_TERMS.map((term, i) => ( +
+ + {term.name} + {term.type === 't2' && i < count && ( + IT2 + )} +
+ ))} +
+
+
+ ); +} + +// ── Main component ─────────────────────────────────────────────────────────── + +export default function AuthDemoPanel() { + const [step, setStep] = useState(1); + const [count, setCount] = useState(0); + const [fading, setFading] = useState(false); + const innerTimerRef = useRef(null); + + const transitionTo = useCallback((nextStep) => { + if (innerTimerRef.current) clearTimeout(innerTimerRef.current); + setFading(true); + innerTimerRef.current = setTimeout(() => { + setStep(nextStep); + setCount(0); + setFading(false); + }, 440); + }, []); + + useEffect(() => { + let timeout; + if (step === 1) { + if (count < STEP1_CARDS.length) { + timeout = setTimeout(() => setCount(c => c + 1), 580); + } else { + timeout = setTimeout(() => transitionTo(2), 950); + } + } else if (step === 2) { + if (count < STEP2_TERMS.length) { + timeout = setTimeout(() => setCount(c => c + 1), 920); + } else { + timeout = setTimeout(() => transitionTo(3), 1300); + } + } else if (step === 3) { + if (count < STEP3_TERMS.length) { + timeout = setTimeout(() => setCount(c => c + 1), 920); + } else { + timeout = setTimeout(() => transitionTo(1), 2800); + } + } + return () => clearTimeout(timeout); + }, [step, count, transitionTo]); + + useEffect(() => { + return () => { if (innerTimerRef.current) clearTimeout(innerTimerRef.current); }; + }, []); + + return ( +
+ {/* Step breadcrumb */} +
+ {STEP_LABELS.map((s, i) => ( + +
+ s.n ? 'bg-emerald-500 text-white' : 'bg-slate-200 text-slate-400'}`}> + {step > s.n ? '✓' : s.n} + + s.n ? 'text-emerald-600' : 'text-slate-400'}`}> + {s.label} + +
+ {i < STEP_LABELS.length - 1 && ( + + )} +
+ ))} + + + En vivo + +
+ +
+ {step === 1 && } + {step === 2 && } + {step === 3 && } +
+
+ ); +} diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx index c91df6a..2fd4743 100644 --- a/frontend/src/pages/Login.jsx +++ b/frontend/src/pages/Login.jsx @@ -4,194 +4,7 @@ import { useAuth } from '../context/AuthContext'; import { authService } from '../services/authService'; import { API_BASE_URL } from '../config'; import { FiArrowLeft, FiEye, FiEyeOff } from 'react-icons/fi'; -import { ComposedChart, Line, XAxis, YAxis, CartesianGrid, ReferenceArea, ReferenceLine, ResponsiveContainer } from 'recharts'; - -const DEMO_TERMS = [ - { name: 'Muy Bajo', xVal: 0.10, mf: { supportStart: 0.00, coreStart: 0.00, coreEnd: 0.10, supportEnd: 0.22 } }, - { name: 'Bajo', xVal: 0.28, mf: { supportStart: 0.12, coreStart: 0.22, coreEnd: 0.33, supportEnd: 0.44 } }, - { name: 'Medio', xVal: 0.50, mf: { supportStart: 0.35, coreStart: 0.44, coreEnd: 0.56, supportEnd: 0.65 } }, - { name: 'Alto', xVal: 0.72, mf: { supportStart: 0.56, coreStart: 0.67, coreEnd: 0.78, supportEnd: 0.88 } }, - { name: 'Muy Alto', xVal: 0.90, mf: { supportStart: 0.78, coreStart: 0.90, coreEnd: 1.00, supportEnd: 1.00 } }, -]; -const DEMO_COLORS = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6', '#d946ef']; - -function FakeDemoPanel() { - const [visibleCount, setVisibleCount] = useState(0); - const [fading, setFading] = useState(false); - - useEffect(() => { - let timeout; - if (visibleCount < DEMO_TERMS.length) { - timeout = setTimeout(() => setVisibleCount(v => v + 1), 1300); - } else { - timeout = setTimeout(() => { - setFading(true); - setTimeout(() => { - setVisibleCount(0); - setFading(false); - }, 500); - }, 2400); - } - return () => clearTimeout(timeout); - }, [visibleCount]); - - const visibleTerms = DEMO_TERMS.slice(0, visibleCount); - const activeIndex = visibleCount - 1; - - return ( -
- {/* Mini header badge */} -
- Paso 2 - · - Modelar Conceptos Difusos - - - En vivo - -
- - {/* Term pills */} -
- {DEMO_TERMS.map((term, index) => { - const color = DEMO_COLORS[index % DEMO_COLORS.length]; - const isVisible = index < visibleCount; - const isActive = index === activeIndex; - return ( - - {term.name} - - ); - })} -
- - {/* Chart */} -
- - - - - - {visibleTerms.map((term, index) => { - const color = DEMO_COLORS[index % DEMO_COLORS.length]; - const isActive = index === activeIndex; - return ( - - ); - })} - {visibleTerms.map((term, index) => { - const color = DEMO_COLORS[index % DEMO_COLORS.length]; - const isActive = index === activeIndex; - return ( - - ); - })} - {visibleTerms.map((term, index) => { - const color = DEMO_COLORS[index % DEMO_COLORS.length]; - const isActive = index === activeIndex; - const trapezeData = [ - { x: term.mf.supportStart, y: 0 }, - { x: term.mf.coreStart, y: 1 }, - { x: term.mf.coreEnd, y: 1 }, - { x: term.mf.supportEnd, y: 0 }, - ]; - return ( - - ); - })} - - -
- - {/* Terms table */} -
-
- Términos modelados - {visibleCount} / {DEMO_TERMS.length} -
-
- {DEMO_TERMS.map((term, index) => { - const isVisible = index < visibleCount; - const isActive = index === activeIndex; - const color = DEMO_COLORS[index % DEMO_COLORS.length]; - return ( -
- - - {term.name} - - {isVisible && ( - - [{term.mf.coreStart.toFixed(2)}, {term.mf.coreEnd.toFixed(2)}] - - )} - {isActive && ( - - )} -
- ); - })} -
-
-
- ); -} +import AuthDemoPanel from '../components/AuthDemoPanel'; export default function Login() { const [email, setEmail] = useState(''); @@ -261,7 +74,8 @@ export default function Login() { return (
-
+ +

Deck of Cards

Modela y compara de forma visual

@@ -274,7 +88,7 @@ export default function Login() { Ir al editor principal - +

@@ -290,41 +104,41 @@ export default function Login() { )}
-
- - setEmail(e.target.value)} - className="w-full px-5 py-3 rounded-2xl border border-slate-200 focus:ring-2 focus:ring-blue-500 outline-none transition-all bg-slate-50 focus:bg-white" - placeholder="correo@ejemplo.com" - /> -
- -
- -
- setPassword(e.target.value)} - className="w-full pl-5 pr-12 py-3 rounded-2xl border border-slate-200 focus:ring-2 focus:ring-blue-500 outline-none transition-all bg-slate-50 focus:bg-white" - placeholder="••••••••" +
+ + setEmail(e.target.value)} + className="w-full px-5 py-3 rounded-2xl border border-slate-200 focus:ring-2 focus:ring-blue-500 outline-none transition-all bg-slate-50 focus:bg-white" + placeholder="correo@ejemplo.com" /> -
-
+ +
+ +
+ setPassword(e.target.value)} + className="w-full pl-5 pr-12 py-3 rounded-2xl border border-slate-200 focus:ring-2 focus:ring-blue-500 outline-none transition-all bg-slate-50 focus:bg-white" + placeholder="••••••••" + /> + +
+
- +
@@ -339,7 +153,8 @@ export default function Login() {

¿Nuevo por aquí? Crea una cuenta

+
); -} \ No newline at end of file +} diff --git a/frontend/src/pages/Register.jsx b/frontend/src/pages/Register.jsx index 09174f4..f4b195f 100644 --- a/frontend/src/pages/Register.jsx +++ b/frontend/src/pages/Register.jsx @@ -3,6 +3,7 @@ import { useNavigate, Link } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; import { authService } from '../services/authService'; import { FiArrowLeft, FiEye, FiEyeOff } from 'react-icons/fi'; +import AuthDemoPanel from '../components/AuthDemoPanel'; export default function Register() { const [username, setUsername] = useState(''); @@ -97,21 +98,22 @@ export default function Register() { return (
-
+

Deck of Cards

Crea tu cuenta y guarda cada modelo

-

+

Registra tus criterios, conserva resultados en el historial y retoma tus análisis cuando quieras.

Ir al editor principal +
From 180114ce38b38820a3dd2b1d87619b26ba029c58 Mon Sep 17 00:00:00 2001 From: Alexis Date: Fri, 29 May 2026 10:49:40 +0200 Subject: [PATCH 3/3] Update AuthDemoPanel component: refine comments for clarity, adjust 'Alto' values in STEP3_TERMS, and enhance layout of Step2Content for better user experience. --- frontend/src/components/AuthDemoPanel.jsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/AuthDemoPanel.jsx b/frontend/src/components/AuthDemoPanel.jsx index 05efddd..ea53ebf 100644 --- a/frontend/src/components/AuthDemoPanel.jsx +++ b/frontend/src/components/AuthDemoPanel.jsx @@ -32,12 +32,12 @@ function trapVal(x, s0, c0, c1, s1) { return 0; } -// Solo 'Alto' es IT2 (tiene subescala). LMF es triángulo → banda "lente" visible. +// Solo 'Alto' es IT2 (tiene subescala). UMF más ancha; LMF con el mismo núcleo trapecial que el paso 2. const STEP3_TERMS = [ { name: 'Bajo', color: '#ef4444', type: 't1', pts: [0.00, 0.00, 0.06, 0.30] }, { name: 'Medio', color: '#f59e0b', type: 't1', pts: [0.18, 0.38, 0.38, 0.59] }, - { name: 'Alto', color: '#10b981', type: 't2', u: [0.49, 0.57, 0.71, 0.81], l: [0.56, 0.64, 0.64, 0.76] }, - { name: 'Perfecto', color: '#3b82f6', type: 't1', pts: [0.74, 0.88, 1.00, 1.00] }, + { name: 'Alto', color: '#10b981', type: 't2', u: [0.49, 0.57, 0.71, 0.81], l: [0.56, 0.60, 0.69, 0.76] }, + { name: 'Perfecto', color: '#3b82f6', type: 't1', pts: [0.74, 0.88, 1.00, 1.00] }, ]; // Puntos clave del trapecio (piezas lineales → basta con vértices, como en el paso 2). @@ -127,7 +127,7 @@ function Step2Content({ count }) { const showSubscale = count >= 3; return ( -
+
{STEP2_TERMS.map((term, i) => { const color = STEP2_COLORS[i % STEP2_COLORS.length]; @@ -217,7 +217,7 @@ function Step2Content({ count }) {
{/* Mini SubscaleModal inline */} -
+
Diseñar Subescala · @@ -397,7 +397,7 @@ export default function AuthDemoPanel() {
-
+
{step === 1 && } {step === 2 && } {step === 3 && }