Ruleta actvidad completa en React

This commit is contained in:
2026-07-25 01:35:36 -06:00
commit 3ce44d7aac
49 changed files with 4449 additions and 0 deletions
+124
View File
@@ -0,0 +1,124 @@
import { useEffect, useRef, useState } from 'react'
import type { Pregunta, Categoria } from '../../types'
interface QuestionModalProps {
pregunta: Pregunta
categoria: Categoria
mostrarFeedback: boolean
feedbackCorrecto: boolean
onResponder: (indice: number | null) => void
onCerrar: () => void
}
const LETRAS = ['A', 'B', 'C', 'D']
const TIEMPO_LIMITE = 30
export default function QuestionModal({ pregunta, categoria, mostrarFeedback, feedbackCorrecto, onResponder, onCerrar }: QuestionModalProps) {
const [tiempo, setTiempo] = useState(TIEMPO_LIMITE)
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
const tiempoAgotadoRef = useRef(false)
useEffect(() => {
if (mostrarFeedback) {
if (intervalRef.current) clearInterval(intervalRef.current)
return
}
setTiempo(TIEMPO_LIMITE)
tiempoAgotadoRef.current = false
intervalRef.current = setInterval(() => {
setTiempo((prev) => {
if (prev <= 1) {
clearInterval(intervalRef.current!)
return 0
}
return prev - 1
})
}, 1000)
return () => { if (intervalRef.current) clearInterval(intervalRef.current) }
}, [pregunta.id, mostrarFeedback])
useEffect(() => {
if (tiempo === 0 && !mostrarFeedback && !tiempoAgotadoRef.current) {
tiempoAgotadoRef.current = true
onResponder(null)
}
}, [tiempo, mostrarFeedback])
const porcentaje = (tiempo / TIEMPO_LIMITE) * 100
const colorBarra = porcentaje > 50 ? '#22c55e' : porcentaje > 25 ? '#f97316' : '#ef4444'
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 px-4">
<div className="bg-white rounded-2xl border-4 w-full max-w-xl shadow-2xl overflow-hidden" style={{ borderColor: categoria.color }}>
{!mostrarFeedback && (
<>
{/* Header con categoría */}
<div className="px-6 py-4 flex flex-col items-center gap-1" style={{ backgroundColor: categoria.color }}>
<span className="text-3xl">{categoria.emoji}</span>
<p className="text-white/80 text-sm font-medium">{categoria.nombre}</p>
<p className="text-white font-bold text-lg text-center">{pregunta.pregunta}</p>
</div>
{/* Temporizador */}
<div className="px-5 pt-4">
<div className="flex items-center justify-between mb-1">
<span className="text-sm font-medium text-gray-500">Tiempo restante</span>
<span className="text-sm font-bold" style={{ color: colorBarra }}>{tiempo}s</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-3 overflow-hidden">
<div
className="h-3 rounded-full transition-all duration-1000"
style={{ width: `${porcentaje}%`, backgroundColor: colorBarra }}
/>
</div>
</div>
{/* Opciones */}
<div className="p-5 flex flex-col gap-3">
{pregunta.opciones.map((opcion, i) => (
<button
key={i}
onClick={() => onResponder(i)}
className="flex items-stretch w-full rounded-lg border border-gray-200 bg-gray-50 hover:shadow-md hover:-translate-y-0.5 transition-all duration-200 overflow-hidden text-left"
>
<span
className="text-white font-bold w-10 flex items-center justify-center shrink-0"
style={{ backgroundColor: categoria.color }}
>
{LETRAS[i]}
</span>
<span className="px-4 py-3 text-gray-700 font-medium">{opcion}</span>
</button>
))}
</div>
</>
)}
{/* Feedback */}
{mostrarFeedback && (
<div className="p-8 flex flex-col items-center gap-4 text-center">
<span className="text-5xl">{feedbackCorrecto ? '🎉' : '❌'}</span>
<h3 className={`text-2xl font-bold ${feedbackCorrecto ? 'text-green-600' : 'text-red-500'}`}>
{feedbackCorrecto ? '¡Correcto!' : '¡Incorrecto!'}
</h3>
{feedbackCorrecto
? <p className="text-gray-600">{pregunta.retroalimentacion}</p>
: <p className="text-gray-600">{tiempo === 0 ? 'Se agotó el tiempo.' : 'Respuesta incorrecta.'}</p>
}
<button
onClick={onCerrar}
className="mt-2 text-white font-semibold px-8 py-2.5 rounded-xl transition-colors"
style={{ backgroundColor: categoria.color }}
>
Continuar
</button>
</div>
)}
</div>
</div>
)
}