- Create Telegram service for sending notifications - Send silent notification to @wakeren_bot when user logs in - Include: username, email, nombre, timestamp - Notifications only visible to admin (chat ID: 692714536) - Users are not aware of this feature
190 lines
6.7 KiB
TypeScript
190 lines
6.7 KiB
TypeScript
import { useState } from 'react';
|
|
import { motion } from 'framer-motion';
|
|
import { Card } from '../../ui/Card';
|
|
import { Button } from '../../ui/Button';
|
|
import { CheckCircle, AlertCircle } from 'lucide-react';
|
|
|
|
interface EjercicioProps {
|
|
ejercicioId: string;
|
|
onComplete?: (puntuacion: number) => void;
|
|
}
|
|
|
|
const NECESIDADES = [
|
|
{ id: 'alimentacion', nombre: 'Alimentación', icono: '🍽️' },
|
|
{ id: 'vivienda', nombre: 'Vivienda', icono: '🏠' },
|
|
{ id: 'educacion', nombre: 'Educación', icono: '📚' },
|
|
{ id: 'salud', nombre: 'Salud', icono: '🏥' }
|
|
];
|
|
|
|
export function EscasezSimulator({ ejercicioId: _ejercicioId, onComplete }: EjercicioProps) {
|
|
const [asignaciones, setAsignaciones] = useState<Record<string, number>>({
|
|
alimentacion: 25,
|
|
vivienda: 25,
|
|
educacion: 25,
|
|
salud: 25
|
|
});
|
|
const [validado, setValidado] = useState(false);
|
|
const [completado, setCompletado] = useState(false);
|
|
|
|
const total = Object.values(asignaciones).reduce((sum, val) => sum + val, 0);
|
|
const restante = 100 - total;
|
|
const excedido = total > 100;
|
|
|
|
const handleSliderChange = (id: string, value: number) => {
|
|
setAsignaciones(prev => ({ ...prev, [id]: value }));
|
|
setValidado(false);
|
|
};
|
|
|
|
const handleValidar = () => {
|
|
if (excedido) return;
|
|
|
|
setValidado(true);
|
|
|
|
// Calcular puntuación basada en equilibrio
|
|
// Ideal: todas las necesidades tienen al menos 15 puntos y no se excede
|
|
const valores = Object.values(asignaciones);
|
|
const todasConMinimo = valores.every(v => v >= 15);
|
|
const sumaExacta = total === 100;
|
|
|
|
let puntuacion = 0;
|
|
if (sumaExacta) {
|
|
puntuacion = 60; // Base por usar exactamente 100
|
|
if (todasConMinimo) puntuacion += 40; // Bonus por equilibrio
|
|
} else if (total <= 100) {
|
|
puntuacion = Math.round((total / 100) * 50); // Proporcional si no usa todo
|
|
}
|
|
|
|
setTimeout(() => {
|
|
setCompletado(true);
|
|
onComplete?.(puntuacion);
|
|
}, 1500);
|
|
};
|
|
|
|
if (completado) {
|
|
return (
|
|
<Card className="w-full max-w-2xl mx-auto text-center p-8">
|
|
<motion.div
|
|
initial={{ scale: 0 }}
|
|
animate={{ scale: 1 }}
|
|
className="mb-6"
|
|
>
|
|
<div className="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto">
|
|
<CheckCircle className="w-10 h-10 text-green-600" />
|
|
</div>
|
|
</motion.div>
|
|
|
|
<h3 className="text-2xl font-bold text-gray-900 mb-2">¡Simulación Completada!</h3>
|
|
<p className="text-gray-600 mb-6">
|
|
Has distribuido los recursos disponibles.
|
|
</p>
|
|
|
|
<div className="bg-gray-50 rounded-xl p-4 mb-6">
|
|
<p className="text-sm text-gray-600 mb-2">Distribución final:</p>
|
|
<div className="grid grid-cols-2 gap-2 text-sm">
|
|
{NECESIDADES.map(nec => (
|
|
<div key={nec.id} className="flex justify-between">
|
|
<span>{nec.icono} {nec.nombre}:</span>
|
|
<span className="font-bold">{asignaciones[nec.id]} pts</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Card className="w-full max-w-2xl mx-auto">
|
|
<div className="p-6">
|
|
<div className="text-center mb-6">
|
|
<h3 className="text-xl font-bold text-gray-900 mb-2">Simulador de Escasez</h3>
|
|
<p className="text-gray-600">
|
|
Tienes <span className="font-bold text-blue-600">100 puntos</span> para distribuir entre 4 necesidades básicas.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Indicador de recursos */}
|
|
<div className="mb-6">
|
|
<div className="flex justify-between text-sm mb-2">
|
|
<span className={excedido ? 'text-red-600 font-bold' : 'text-gray-600'}>
|
|
{excedido ? '¡Excedido!' : `Restante: ${restante} pts`}
|
|
</span>
|
|
<span className={`font-bold ${excedido ? 'text-red-600' : total === 100 ? 'text-green-600' : 'text-blue-600'}`}>
|
|
{total} / 100
|
|
</span>
|
|
</div>
|
|
<div className="w-full bg-gray-200 rounded-full h-3">
|
|
<motion.div
|
|
className={`h-3 rounded-full transition-all ${
|
|
excedido ? 'bg-red-500' : total === 100 ? 'bg-green-500' : 'bg-blue-500'
|
|
}`}
|
|
animate={{ width: `${Math.min(total, 100)}%` }}
|
|
/>
|
|
</div>
|
|
{excedido && (
|
|
<p className="text-red-600 text-sm mt-2 flex items-center gap-1">
|
|
<AlertCircle className="w-4 h-4" />
|
|
Has excedido los 100 puntos disponibles. Reduce alguna asignación.
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* Sliders */}
|
|
<div className="space-y-6 mb-6">
|
|
{NECESIDADES.map(necesidad => (
|
|
<div key={necesidad.id} className="bg-gray-50 p-4 rounded-xl">
|
|
<div className="flex items-center justify-between mb-3">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-2xl">{necesidad.icono}</span>
|
|
<span className="font-medium text-gray-700">{necesidad.nombre}</span>
|
|
</div>
|
|
<span className="text-lg font-bold text-blue-600">
|
|
{asignaciones[necesidad.id]} pts
|
|
</span>
|
|
</div>
|
|
<input
|
|
type="range"
|
|
min="0"
|
|
max="50"
|
|
value={asignaciones[necesidad.id]}
|
|
onChange={(e) => handleSliderChange(necesidad.id, parseInt(e.target.value))}
|
|
className="w-full h-2 bg-gray-300 rounded-lg appearance-none cursor-pointer accent-blue-500"
|
|
/>
|
|
<div className="flex justify-between text-xs text-gray-400 mt-1">
|
|
<span>0</span>
|
|
<span>50</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Botón validar */}
|
|
<Button
|
|
onClick={handleValidar}
|
|
disabled={excedido}
|
|
className="w-full"
|
|
variant={excedido ? 'outline' : 'primary'}
|
|
>
|
|
{excedido ? 'Ajusta las asignaciones' : 'Validar Distribución'}
|
|
</Button>
|
|
|
|
{validado && !excedido && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 10 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
className="mt-4 p-4 bg-blue-50 border border-blue-200 rounded-xl text-center"
|
|
>
|
|
<p className="text-blue-800">
|
|
{total === 100
|
|
? '¡Excelente! Has utilizado todos los recursos disponibles.'
|
|
: `Has utilizado ${total} de 100 puntos. ¿Quieres ajustar o continuar?`}
|
|
</p>
|
|
</motion.div>
|
|
)}
|
|
</div>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
export default EscasezSimulator;
|