feat: Ajuste de ejecucion de ruleta, aplicacion de CC
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 318 KiB After Width: | Height: | Size: 459 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 665 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.9 MiB After Width: | Height: | Size: 1.8 MiB |
@@ -77,8 +77,8 @@ export default function FeedbackModal({
|
|||||||
<div
|
<div
|
||||||
className="flex flex-col justify-center transition-all duration-300"
|
className="flex flex-col justify-center transition-all duration-300"
|
||||||
style={{
|
style={{
|
||||||
width: '1700px',
|
width: '1100px',
|
||||||
maxWidth: '1824px',
|
maxWidth: '1100px',
|
||||||
height: '594px',
|
height: '594px',
|
||||||
background: 'linear-gradient(180deg, #ffffff 0%, #d8dcec 100%)',
|
background: 'linear-gradient(180deg, #ffffff 0%, #d8dcec 100%)',
|
||||||
borderRadius: '18px',
|
borderRadius: '18px',
|
||||||
@@ -128,7 +128,7 @@ export default function FeedbackModal({
|
|||||||
<div className="px-12 pt-10 pb-6">
|
<div className="px-12 pt-10 pb-6">
|
||||||
<p
|
<p
|
||||||
className="leading-relaxed"
|
className="leading-relaxed"
|
||||||
style={{ color: '#333333', fontSize: '22px', lineHeight: '1.6' }}
|
style={{ color: '#333333', fontSize: '26px', lineHeight: '1.6' }}
|
||||||
>
|
>
|
||||||
<strong>Reflexión:</strong> {reflexion}
|
<strong>Reflexión:</strong> {reflexion}
|
||||||
</p>
|
</p>
|
||||||
@@ -144,7 +144,7 @@ export default function FeedbackModal({
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="font-bold text-white whitespace-nowrap"
|
className="font-bold text-white whitespace-nowrap"
|
||||||
style={{ fontSize: '28px' }}
|
style={{ fontSize: '32px' }}
|
||||||
>
|
>
|
||||||
¿Sabías qué?
|
¿Sabías qué?
|
||||||
</span>
|
</span>
|
||||||
@@ -152,7 +152,7 @@ export default function FeedbackModal({
|
|||||||
<div className="flex-1 px-6 py-5">
|
<div className="flex-1 px-6 py-5">
|
||||||
<p
|
<p
|
||||||
className="leading-relaxed"
|
className="leading-relaxed"
|
||||||
style={{ color: '#444444', fontSize: '22px', lineHeight: '1.6' }}
|
style={{ color: '#444444', fontSize: '26px', lineHeight: '1.6' }}
|
||||||
>
|
>
|
||||||
{datoCurioso}
|
{datoCurioso}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -71,8 +71,8 @@ export default function RuletaSVG({ categorias, rotacion, isSpinning, onClick }:
|
|||||||
return (
|
return (
|
||||||
<div className="relative flex flex-col items-center justify-center w-full select-none">
|
<div className="relative flex flex-col items-center justify-center w-full select-none">
|
||||||
|
|
||||||
{/* ─── Puntero (fuera del SVG, fijo arriba) ─── */}
|
{/* ─── Puntero ─── */}
|
||||||
<div className="absolute top-0 left-1/2 -translate-x-1/2 z-20" style={{ marginTop: '35px' }}>
|
<div className="absolute top-0 left-1/2 -translate-x-1/2 z-20" style={{ marginTop: '0px' }}>
|
||||||
<img
|
<img
|
||||||
src="./img/indicador.png"
|
src="./img/indicador.png"
|
||||||
alt="Indicador"
|
alt="Indicador"
|
||||||
@@ -81,7 +81,7 @@ export default function RuletaSVG({ categorias, rotacion, isSpinning, onClick }:
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ─── SVG principal (ruleta + anillo + soporte) ─── */}
|
{/* ─── SVG principal ─── */}
|
||||||
<svg
|
<svg
|
||||||
viewBox={`0 0 ${VIEWBOX_W} ${VIEWBOX_H}`}
|
viewBox={`0 0 ${VIEWBOX_W} ${VIEWBOX_H}`}
|
||||||
onClick={!isSpinning ? onClick : undefined}
|
onClick={!isSpinning ? onClick : undefined}
|
||||||
@@ -101,7 +101,8 @@ export default function RuletaSVG({ categorias, rotacion, isSpinning, onClick }:
|
|||||||
{/* ══════ GRUPO GIRATORIO: Segmentos + centro ══════ */}
|
{/* ══════ GRUPO GIRATORIO: Segmentos + centro ══════ */}
|
||||||
<g
|
<g
|
||||||
style={{
|
style={{
|
||||||
transformOrigin: `${CX}px ${CY}px`,
|
transformBox: 'fill-box',
|
||||||
|
transformOrigin: 'center center',
|
||||||
transform: `rotate(${rotacion}deg)`,
|
transform: `rotate(${rotacion}deg)`,
|
||||||
transition: 'transform 4s cubic-bezier(0.17, 0.67, 0.12, 0.99)',
|
transition: 'transform 4s cubic-bezier(0.17, 0.67, 0.12, 0.99)',
|
||||||
}}
|
}}
|
||||||
@@ -120,7 +121,8 @@ export default function RuletaSVG({ categorias, rotacion, isSpinning, onClick }:
|
|||||||
const textOffsetX = i === 0 ? 0 : i === 1 ? -40 : i === 2 ? 30 : i === 3 ? 38 : 0
|
const textOffsetX = i === 0 ? 0 : i === 1 ? -40 : i === 2 ? 30 : i === 3 ? 38 : 0
|
||||||
const textOffsetY = i === 0 ? -45 : i === 1 ? 10 : i === 3 ? -12 : 0
|
const textOffsetY = i === 0 ? -45 : i === 1 ? 10 : i === 3 ? -12 : 0
|
||||||
const textPos = { x: textPosRaw.x + textOffsetX, y: textPosRaw.y + textOffsetY }
|
const textPos = { x: textPosRaw.x + textOffsetX, y: textPosRaw.y + textOffsetY }
|
||||||
const imgSize = 60
|
const imgSize = i === 1 ? 58 : i === 2 ? 95 : i === 3 ? 88 : 70
|
||||||
|
const imgOffsetY = i === 0 ? 12 : i === 3 ? 15 : 0
|
||||||
|
|
||||||
/* Imágenes por segmento */
|
/* Imágenes por segmento */
|
||||||
const imgs = ['agua.png', 'residuos.png', 'energia.png', 'movilidad.png']
|
const imgs = ['agua.png', 'residuos.png', 'energia.png', 'movilidad.png']
|
||||||
@@ -142,7 +144,7 @@ export default function RuletaSVG({ categorias, rotacion, isSpinning, onClick }:
|
|||||||
<image
|
<image
|
||||||
href={`./img/${imgs[i]}`}
|
href={`./img/${imgs[i]}`}
|
||||||
x={imgCenter.x - imgSize / 2}
|
x={imgCenter.x - imgSize / 2}
|
||||||
y={imgCenter.y - imgSize / 2}
|
y={imgCenter.y - imgSize / 2 + imgOffsetY}
|
||||||
width={imgSize}
|
width={imgSize}
|
||||||
height={imgSize}
|
height={imgSize}
|
||||||
preserveAspectRatio="xMidYMid meet"
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ export default function Header() {
|
|||||||
<header
|
<header
|
||||||
className="w-full bg-transparent flex items-center shrink-0"
|
className="w-full bg-transparent flex items-center shrink-0"
|
||||||
style={{
|
style={{
|
||||||
height: '90px',
|
height: '130px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Logo izquierdo */}
|
{/* Logo izquierdo */}
|
||||||
<div style={{ marginLeft: '75px', display: 'flex', alignItems: 'center' }}>
|
<div style={{ marginLeft: '75px', display: 'flex', alignItems: 'center' }}>
|
||||||
<img src="./img/logo.png" alt="TIP" style={{ height: '50px', width: 'auto' }} />
|
<img src="./img/logo.png" alt="TIP" style={{ height: '110px', width: 'auto' }} />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -32,15 +32,17 @@ export default function Cover({ onStart }: CoverProps) {
|
|||||||
className="rounded-3xl p-8 shadow-lg"
|
className="rounded-3xl p-8 shadow-lg"
|
||||||
style={{ background: 'linear-gradient(180deg, #ffffff 0%, #d8dcec 100%)' }}
|
style={{ background: 'linear-gradient(180deg, #ffffff 0%, #d8dcec 100%)' }}
|
||||||
>
|
>
|
||||||
<p className="text-gray-700 leading-relaxed" style={{ fontSize: '22px' }}>
|
<p className="text-gray-700 leading-relaxed" style={{ fontSize: '26px' }}>
|
||||||
Las reglas son muy sencillas: la ruleta girará, la suerte elegirá una categoría
|
Las reglas son muy sencillas: la ruleta girará, la suerte elegirá una categoría
|
||||||
y te tocará responder una pregunta. Contarás con 30 segundos para responder
|
y te tocará responder una pregunta. Contarás con 30 segundos para responder
|
||||||
cada una.
|
cada una.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-700 leading-relaxed mt-4" style={{ fontSize: '22px' }}>
|
<p className="text-gray-700 leading-relaxed mt-4" style={{ fontSize: '26px' }}>
|
||||||
No se trata de encontrar la respuesta perfecta, sino de reflexionar y responder
|
No se trata de encontrar la respuesta perfecta, sino de reflexionar y responder
|
||||||
desde tu experiencia y perspectiva. ¡Prepárate para poner a prueba tus
|
desde tu experiencia y perspectiva.
|
||||||
conocimientos y disfrutar de la dinámica!
|
</p>
|
||||||
|
<p className="text-gray-700 leading-relaxed mt-4" style={{ fontSize: '26px' }}>
|
||||||
|
¡Prepárate para poner a prueba tus conocimientos y disfrutar de la dinámica!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ const LETRAS = ['A', 'B', 'C', 'D']
|
|||||||
/** Mapeo de nombre de categoría → imagen PNG */
|
/** Mapeo de nombre de categoría → imagen PNG */
|
||||||
const CAT_IMG: Record<string, string> = {
|
const CAT_IMG: Record<string, string> = {
|
||||||
'Agua': 'agua.png',
|
'Agua': 'agua.png',
|
||||||
'Residuos': 'residuos.png',
|
'Residuos y Circularidad': 'residuos.png',
|
||||||
'Energía': 'energia.png',
|
'Energía': 'energia.png',
|
||||||
'Electromovilidad': 'movilidad.png',
|
'Electromovilidad y Transporte Sostenible': 'movilidad.png',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Game({ onEnd }: GameProps) {
|
export default function Game({ onEnd }: GameProps) {
|
||||||
@@ -44,7 +44,7 @@ export default function Game({ onEnd }: GameProps) {
|
|||||||
backgroundColor: '#ed1f24',
|
backgroundColor: '#ed1f24',
|
||||||
borderRadius: '0px 0px 0px 25px',
|
borderRadius: '0px 0px 0px 25px',
|
||||||
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
|
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
|
||||||
maxWidth: '520px',
|
maxWidth: '700px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Círculo blanco con imagen de categoría */}
|
{/* Círculo blanco con imagen de categoría */}
|
||||||
@@ -52,29 +52,45 @@ export default function Game({ onEnd }: GameProps) {
|
|||||||
<div
|
<div
|
||||||
className="rounded-full flex items-center justify-center shrink-0"
|
className="rounded-full flex items-center justify-center shrink-0"
|
||||||
style={{
|
style={{
|
||||||
width: '52px',
|
width: '80px',
|
||||||
height: '52px',
|
height: '80px',
|
||||||
backgroundColor: '#FFFFFF',
|
backgroundColor: '#FFFFFF',
|
||||||
boxShadow: '0 2px 8px rgba(0,0,0,0.2)',
|
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
|
||||||
|
marginTop: '-19px',
|
||||||
|
marginBottom: '-55px',
|
||||||
|
position: 'relative',
|
||||||
|
zIndex: 30,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={`./img/${CAT_IMG[categoria.nombre]}`}
|
src={`./img/${CAT_IMG[categoria.nombre]}`}
|
||||||
alt={categoria.nombre}
|
alt={categoria.nombre}
|
||||||
className="object-contain"
|
className="object-contain"
|
||||||
style={{ width: '36px', height: '36px' }}
|
style={{ width: '50px', height: '50px' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<span
|
<span
|
||||||
className="text-white font-extrabold tracking-wide py-3 whitespace-nowrap"
|
className="text-white font-extrabold tracking-wide py-3 whitespace-nowrap"
|
||||||
style={{ fontSize: '22px' }}
|
style={{ fontSize: categoria.nombre.length > 20 ? '26px' : '30px' }}
|
||||||
>
|
>
|
||||||
{categoria.nombre}
|
{categoria.nombre}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* ─── Contador de preguntas (bajo la barra roja) ─── */}
|
||||||
|
<div className="absolute top-0 right-0 z-10" style={{ marginTop: '72px' }}>
|
||||||
|
<div
|
||||||
|
className="px-4 py-1"
|
||||||
|
style={{ backgroundColor: '#000000', borderRadius: '10px 0px 0px 10px' }}
|
||||||
|
>
|
||||||
|
<span className="text-white font-bold" style={{ fontSize: '26px' }}>
|
||||||
|
{Math.min(estado.preguntasRespondidas + 1, MAX_PREGUNTAS)}/{MAX_PREGUNTAS}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* ─── Contenido principal: Ruleta + Panel de pregunta ─── */}
|
{/* ─── Contenido principal: Ruleta + Panel de pregunta ─── */}
|
||||||
<div className="flex-1 flex items-center justify-center min-h-0">
|
<div className="flex-1 flex items-center justify-center min-h-0">
|
||||||
|
|
||||||
@@ -148,10 +164,10 @@ export default function Game({ onEnd }: GameProps) {
|
|||||||
background: 'linear-gradient(180deg, #ffffff 0%, #d8dcec 100%)',
|
background: 'linear-gradient(180deg, #ffffff 0%, #d8dcec 100%)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<p className="text-gray-700 font-medium" style={{ fontSize: '22px' }}>
|
<p className="text-gray-700 font-medium" style={{ fontSize: '26px' }}>
|
||||||
Gira la ruleta para obtener una pregunta
|
Gira la ruleta para obtener una pregunta
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-500 mt-2" style={{ fontSize: '22px' }}>
|
<p className="text-gray-500 mt-2" style={{ fontSize: '26px' }}>
|
||||||
Cada respuesta correcta te acerca al objetivo de {MAX_PREGUNTAS} preguntas
|
Cada respuesta correcta te acerca al objetivo de {MAX_PREGUNTAS} preguntas
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -139,11 +139,14 @@ export default function Intro() {
|
|||||||
|
|
||||||
{/* ─── Columna derecha (40%): texto ─────────── */}
|
{/* ─── Columna derecha (40%): texto ─────────── */}
|
||||||
<div className="flex flex-col justify-center" style={{ width: '35%', paddingRight: '20px' }}>
|
<div className="flex flex-col justify-center" style={{ width: '35%', paddingRight: '20px' }}>
|
||||||
<div className="relative z-10 w-full bg-transparent rounded-3xl p-10" style={{top: '-10%'}}>
|
<div className="relative z-10 w-full bg-transparent rounded-3xl p-10" style={{ top: 'calc(-10% - 65px)' }}>
|
||||||
{/* Texto introductorio */}
|
{/* Texto introductorio */}
|
||||||
<p className="text-gray-700 leading-relaxed mb-8" style={{ fontSize: '22px' }}>
|
<p className="text-gray-700 leading-relaxed mb-6" style={{ fontSize: '26px' }}>
|
||||||
Hoy vamos a poner a prueba nuestros conocimientos y a reflexionar juntos sobre
|
Hoy vamos a poner a prueba nuestros conocimientos y a reflexionar juntos sobre
|
||||||
cómo nuestras acciones impactan el planeta. En esta ruleta tenemos{' '}
|
cómo nuestras acciones impactan el planeta.
|
||||||
|
</p>
|
||||||
|
<p className="text-gray-700 leading-relaxed mb-8" style={{ fontSize: '26px' }}>
|
||||||
|
En esta ruleta tenemos{' '}
|
||||||
<strong>4 ejes fundamentales para el futuro:</strong>
|
<strong>4 ejes fundamentales para el futuro:</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -157,10 +160,13 @@ export default function Intro() {
|
|||||||
src={`./img/${imagenes[i]}`}
|
src={`./img/${imagenes[i]}`}
|
||||||
alt={cat.nombre}
|
alt={cat.nombre}
|
||||||
className="shrink-0 object-contain"
|
className="shrink-0 object-contain"
|
||||||
style={{ width: '75px', height: '78px' }}
|
style={{
|
||||||
|
width: i === 1 ? '48px' : '60px',
|
||||||
|
height: i === 1 ? '50px' : '62px',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<strong className="text-gray-800" style={{ fontSize: '22px' }}>
|
<strong className="text-gray-800" style={{ fontSize: '24px' }}>
|
||||||
{cat.nombre}
|
{cat.nombre}
|
||||||
{cat.nombre2 ? ` ${cat.nombre2}` : ''}
|
{cat.nombre2 ? ` ${cat.nombre2}` : ''}
|
||||||
{cat.nombre3 ? ` ${cat.nombre3}` : ''}
|
{cat.nombre3 ? ` ${cat.nombre3}` : ''}
|
||||||
|
|||||||
@@ -45,13 +45,13 @@ export default function Result(_props: ResultProps) {
|
|||||||
>
|
>
|
||||||
<p
|
<p
|
||||||
className="text-gray-700 mb-6"
|
className="text-gray-700 mb-6"
|
||||||
style={{ fontSize: '22px', lineHeight: '1.6' }}
|
style={{ fontSize: '28px', lineHeight: '1.6' }}
|
||||||
>
|
>
|
||||||
Recorrimos desde la gestión del agua y los residuos, hasta la transición hacia energías más limpias y la movilidad del futuro. Más allá de las respuestas correctas o los puntos, lo verdaderamente importante es que la sostenibilidad no se queda en esta pantalla: se construye con las decisiones que tomamos todos los días.
|
Recorrimos desde la gestión del agua y los residuos, hasta la transición hacia energías más limpias y la movilidad del futuro. Más allá de las respuestas correctas o los puntos, lo verdaderamente importante es que la sostenibilidad no se queda en esta pantalla: se construye con las decisiones que tomamos todos los días.
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
className="text-gray-700"
|
className="text-gray-700"
|
||||||
style={{ fontSize: '22px', lineHeight: '1.6', fontWeight: '600' }}
|
style={{ fontSize: '28px', lineHeight: '1.6', fontWeight: '600' }}
|
||||||
>
|
>
|
||||||
Muchísimas gracias a todos por su participación, su energía y sus aportaciones.
|
Muchísimas gracias a todos por su participación, su energía y sus aportaciones.
|
||||||
</p>
|
</p>
|
||||||
@@ -77,7 +77,7 @@ export default function Result(_props: ResultProps) {
|
|||||||
>
|
>
|
||||||
<p
|
<p
|
||||||
className="font-bold text-white"
|
className="font-bold text-white"
|
||||||
style={{ fontSize: '22px', lineHeight: '1.5' }}
|
style={{ fontSize: '28px', lineHeight: '1.5' }}
|
||||||
>
|
>
|
||||||
¡Nos llevamos grandes aprendizajes para seguir cuidando el planeta juntos!
|
¡Nos llevamos grandes aprendizajes para seguir cuidando el planeta juntos!
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
+54
-49
@@ -67,6 +67,17 @@ export function useRuleta() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const rotacionRef = useRef(0)
|
const rotacionRef = useRef(0)
|
||||||
|
const isSpinningRef = useRef(false)
|
||||||
|
const completadoRef = useRef(false)
|
||||||
|
const usadasRef = useRef<Record<number, number>>({})
|
||||||
|
const poolRef = useRef(poolInicial)
|
||||||
|
const girarRef = useRef<() => void>(() => {})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
usadasRef.current = estado.preguntasUsadasPorCategoria
|
||||||
|
poolRef.current = estado.preguntasPool
|
||||||
|
completadoRef.current = estado.completado
|
||||||
|
}, [estado.preguntasUsadasPorCategoria, estado.preguntasPool, estado.completado])
|
||||||
|
|
||||||
// ─── Temporizador por pregunta ───────────────────────────────────────────
|
// ─── Temporizador por pregunta ───────────────────────────────────────────
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -89,6 +100,12 @@ export function useRuleta() {
|
|||||||
feedbackCorrecto: false,
|
feedbackCorrecto: false,
|
||||||
completado,
|
completado,
|
||||||
aprobado,
|
aprobado,
|
||||||
|
preguntasUsadasPorCategoria: {
|
||||||
|
...prev.preguntasUsadasPorCategoria,
|
||||||
|
...(prev.categoriaActual
|
||||||
|
? { [prev.categoriaActual.id]: (prev.preguntasUsadasPorCategoria[prev.categoriaActual.id] ?? 0) + 1 }
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,38 +116,29 @@ export function useRuleta() {
|
|||||||
return () => clearInterval(interval)
|
return () => clearInterval(interval)
|
||||||
}, [estado.preguntaActual?.id, estado.mostrarFeedback, estado.completado])
|
}, [estado.preguntaActual?.id, estado.mostrarFeedback, estado.completado])
|
||||||
|
|
||||||
/**
|
|
||||||
* Busca la siguiente pregunta disponible.
|
|
||||||
* 1. Intenta usar la categoría donde cayó la ruleta (si le quedan preguntas).
|
|
||||||
* 2. Si no, recorre las demás categorías en orden circular.
|
|
||||||
* 3. Retorna null si ya se usaron las 12 preguntas.
|
|
||||||
*/
|
|
||||||
function obtenerSiguientePregunta(
|
|
||||||
catIndice: number,
|
|
||||||
usadasPorCat: Record<number, number>,
|
|
||||||
pool: Record<number, Pregunta[]>,
|
|
||||||
): { pregunta: Pregunta; categoria: Categoria } | null {
|
|
||||||
// Recorrer desde catIndice en círculo
|
|
||||||
for (let vuelta = 0; vuelta < SECCIONES; vuelta++) {
|
|
||||||
const idx = (catIndice + vuelta) % SECCIONES
|
|
||||||
const cat = categorias[idx]
|
|
||||||
const usadas = usadasPorCat[cat.id] ?? 0
|
|
||||||
const preguntasCat = pool[cat.id] ?? []
|
|
||||||
if (usadas < preguntasCat.length) {
|
|
||||||
return { pregunta: preguntasCat[usadas], categoria: cat }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
function girar() {
|
function girar() {
|
||||||
if (estado.isSpinning || estado.completado) return
|
if (isSpinningRef.current || completadoRef.current) return
|
||||||
|
|
||||||
|
const hayDisponibles = categorias.some((cat) => {
|
||||||
|
const usadas = usadasRef.current[cat.id] ?? 0
|
||||||
|
return usadas < (poolRef.current[cat.id]?.length ?? 0)
|
||||||
|
})
|
||||||
|
if (!hayDisponibles) {
|
||||||
|
setEstado((prev) => ({
|
||||||
|
...prev,
|
||||||
|
completado: true,
|
||||||
|
aprobado: prev.correctas >= MIN_CORRECTAS,
|
||||||
|
}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isSpinningRef.current = true
|
||||||
sounds.wheelSpin?.play()
|
sounds.wheelSpin?.play()
|
||||||
|
|
||||||
const vueltas = 3 + Math.random() * 2
|
const vueltas = 3 + Math.random() * 2
|
||||||
const anguloFinal = Math.random() * 360
|
const anguloExtra = Math.random() * 360
|
||||||
const rotacionTotal = rotacionRef.current + vueltas * 360 + anguloFinal
|
const delta = vueltas * 360 + anguloExtra
|
||||||
|
const rotacionTotal = rotacionRef.current + delta
|
||||||
rotacionRef.current = rotacionTotal
|
rotacionRef.current = rotacionTotal
|
||||||
|
|
||||||
setEstado((prev) => ({ ...prev, isSpinning: true, rotacion: rotacionTotal }))
|
setEstado((prev) => ({ ...prev, isSpinning: true, rotacion: rotacionTotal }))
|
||||||
@@ -138,33 +146,29 @@ export function useRuleta() {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const anguloNormalizado = ((360 - (rotacionRef.current % 360)) + 360) % 360
|
const anguloNormalizado = ((360 - (rotacionRef.current % 360)) + 360) % 360
|
||||||
const indiceCategoria = Math.floor(anguloNormalizado / ANGULO_SECCION) % SECCIONES
|
const indiceCategoria = Math.floor(anguloNormalizado / ANGULO_SECCION) % SECCIONES
|
||||||
|
const cat = categorias[indiceCategoria]
|
||||||
|
const usadas = usadasRef.current[cat.id] ?? 0
|
||||||
|
const preguntasCat = poolRef.current[cat.id] ?? []
|
||||||
|
|
||||||
setEstado((prev) => {
|
// Categoría sin preguntas → re-girar
|
||||||
const resultado = obtenerSiguientePregunta(
|
if (usadas >= preguntasCat.length) {
|
||||||
indiceCategoria,
|
isSpinningRef.current = false
|
||||||
prev.preguntasUsadasPorCategoria,
|
setEstado((prev) => ({ ...prev, isSpinning: false }))
|
||||||
prev.preguntasPool,
|
setTimeout(() => girarRef.current(), 600)
|
||||||
)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!resultado) {
|
setEstado((prev) => ({
|
||||||
// Ya no hay preguntas — marcar como completado
|
...prev,
|
||||||
return {
|
preguntaActual: preguntasCat[usadas],
|
||||||
...prev,
|
categoriaActual: cat,
|
||||||
completado: true,
|
tiempoRestante: TIEMPO_LIMITE,
|
||||||
aprobado: prev.correctas >= MIN_CORRECTAS,
|
}))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...prev,
|
|
||||||
preguntaActual: resultado.pregunta,
|
|
||||||
categoriaActual: resultado.categoria,
|
|
||||||
tiempoRestante: TIEMPO_LIMITE,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, 4000)
|
}, 4000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
girarRef.current = girar
|
||||||
|
|
||||||
function responder(indiceOpcion: number) {
|
function responder(indiceOpcion: number) {
|
||||||
if (!estado.preguntaActual || !estado.categoriaActual) return
|
if (!estado.preguntaActual || !estado.categoriaActual) return
|
||||||
const esCorrecta = indiceOpcion === estado.preguntaActual.correcta
|
const esCorrecta = indiceOpcion === estado.preguntaActual.correcta
|
||||||
@@ -194,6 +198,7 @@ export function useRuleta() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cerrarFeedback() {
|
function cerrarFeedback() {
|
||||||
|
isSpinningRef.current = false
|
||||||
setEstado((prev) => ({
|
setEstado((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
preguntaActual: null,
|
preguntaActual: null,
|
||||||
|
|||||||
Reference in New Issue
Block a user