Add Telegram notifications for admin on user login
- 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
This commit is contained in:
@@ -0,0 +1,414 @@
|
||||
import { useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Card } from '../../ui/Card';
|
||||
import { Button } from '../../ui/Button';
|
||||
import { CheckCircle, XCircle, Trophy, RotateCcw, ArrowRight, Mountain, Users, Factory, Lightbulb } from 'lucide-react';
|
||||
|
||||
interface FactoresProduccionQuizProps {
|
||||
ejercicioId: string;
|
||||
onComplete?: (puntuacion: number) => void;
|
||||
}
|
||||
|
||||
interface Pregunta {
|
||||
id: number;
|
||||
pregunta: string;
|
||||
tipo: 'tierra' | 'trabajo' | 'capital' | 'emprendimiento';
|
||||
opciones: string[];
|
||||
respuestaCorrecta: number;
|
||||
explicacion: string;
|
||||
}
|
||||
|
||||
const PREGUNTAS: Pregunta[] = [
|
||||
{
|
||||
id: 1,
|
||||
pregunta: '¿Cuál de los siguientes es un ejemplo de TIERRA como factor de producción?',
|
||||
tipo: 'tierra',
|
||||
opciones: [
|
||||
'El trabajo de un obrero',
|
||||
'Un terreno agrícola',
|
||||
'Una máquina industrial',
|
||||
'La habilidad de un gerente'
|
||||
],
|
||||
respuestaCorrecta: 1,
|
||||
explicacion: 'La tierra incluye todos los recursos naturales: terrenos, minerales, agua, petróleo, etc. Es todo lo que nos proporciona la naturaleza sin transformar.'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
pregunta: 'El TRABAJO como factor de producción se refiere a:',
|
||||
tipo: 'trabajo',
|
||||
opciones: [
|
||||
'Solo el esfuerzo físico',
|
||||
'Solo el esfuerzo mental',
|
||||
'El esfuerzo físico y mental que aportan las personas',
|
||||
'Las máquinas que reemplazan a los humanos'
|
||||
],
|
||||
respuestaCorrecta: 2,
|
||||
explicacion: 'El trabajo incluye tanto el esfuerzo físico (como el de un albañil) como el mental (como el de un ingeniero). Es el factor humano en la producción.'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
pregunta: '¿Qué se considera CAPITAL como factor de producción?',
|
||||
tipo: 'capital',
|
||||
opciones: [
|
||||
'Dinero en una cuenta bancaria',
|
||||
'Acciones de una empresa',
|
||||
'Maquinaria, herramientas y equipos utilizados para producir',
|
||||
'Terrenos y edificios'
|
||||
],
|
||||
respuestaCorrecta: 2,
|
||||
explicacion: 'En economía, el capital físico (o capital real) son los bienes manufacturados utilizados para producir otros bienes: máquinas, herramientas, fábricas, etc. No es dinero.'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
pregunta: '¿Cuál es la recompensa que reciben los propietarios del factor TIERRA?',
|
||||
tipo: 'tierra',
|
||||
opciones: [
|
||||
'Salarios',
|
||||
'Rentas o alquileres',
|
||||
'Intereses',
|
||||
'Beneficios'
|
||||
],
|
||||
respuestaCorrecta: 1,
|
||||
explicacion: 'Los propietarios de tierra reciben RENTAS (o alquileres) como pago por el uso de sus recursos naturales.'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
pregunta: 'Los trabajadores reciben _____ como recompensa por su factor de producción.',
|
||||
tipo: 'trabajo',
|
||||
opciones: [
|
||||
'Intereses',
|
||||
'Rentas',
|
||||
'Salarios',
|
||||
'Dividendos'
|
||||
],
|
||||
respuestaCorrecta: 2,
|
||||
explicacion: 'El trabajo recibe SALARIOS (o sueldos) como compensación por el esfuerzo físico y mental aportado a la producción.'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
pregunta: '¿Qué reciben los propietarios de CAPITAL como recompensa?',
|
||||
tipo: 'capital',
|
||||
opciones: [
|
||||
'Salarios',
|
||||
'Rentas',
|
||||
'Intereses',
|
||||
'Bonificaciones'
|
||||
],
|
||||
respuestaCorrecta: 2,
|
||||
explicacion: 'El capital recibe INTERESES como recompensa. Si prestas tu capital (maquinaria o dinero para comprarla), recibes intereses a cambio.'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
pregunta: 'El EMPRENDIMIENTO (o empresa) es el factor que:',
|
||||
tipo: 'emprendimiento',
|
||||
opciones: [
|
||||
'Solo invierte dinero',
|
||||
'Combina los otros factores de producción asumiendo riesgos',
|
||||
'Trabaja en la fábrica',
|
||||
'Solo vende los productos'
|
||||
],
|
||||
respuestaCorrecta: 1,
|
||||
explicacion: 'El emprendimiento es el factor que organiza y combina tierra, trabajo y capital para producir bienes y servicios, asumiendo el riesgo del negocio.'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
pregunta: '¿Cuál es la recompensa del EMPRENDIMIENTO?',
|
||||
tipo: 'emprendimiento',
|
||||
opciones: [
|
||||
'Salario fijo',
|
||||
'Intereses garantizados',
|
||||
'Beneficios (o pérdidas)',
|
||||
'Renta del terreno'
|
||||
],
|
||||
respuestaCorrecta: 2,
|
||||
explicacion: 'El emprendimiento recibe BENEFICIOS cuando la empresa tiene éxito, pero también puede sufrir PÉRDIDAS. Es el factor con mayor riesgo y potencial de ganancia.'
|
||||
}
|
||||
];
|
||||
|
||||
export function FactoresProduccionQuiz({ ejercicioId: _ejercicioId, onComplete }: FactoresProduccionQuizProps) {
|
||||
const [preguntaActual, setPreguntaActual] = useState(0);
|
||||
const [respuestaSeleccionada, setRespuestaSeleccionada] = useState<number | null>(null);
|
||||
const [mostrarResultado, setMostrarResultado] = useState(false);
|
||||
const [puntuacion, setPuntuacion] = useState(0);
|
||||
const [completado, setCompletado] = useState(false);
|
||||
const [respuestasCorrectas, setRespuestasCorrectas] = useState(0);
|
||||
|
||||
const pregunta = PREGUNTAS[preguntaActual];
|
||||
|
||||
const getTipoIcon = (tipo: string) => {
|
||||
switch (tipo) {
|
||||
case 'tierra':
|
||||
return <Mountain size={20} />;
|
||||
case 'trabajo':
|
||||
return <Users size={20} />;
|
||||
case 'capital':
|
||||
return <Factory size={20} />;
|
||||
case 'emprendimiento':
|
||||
return <Lightbulb size={20} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const getTipoLabel = (tipo: string) => {
|
||||
switch (tipo) {
|
||||
case 'tierra':
|
||||
return 'Tierra';
|
||||
case 'trabajo':
|
||||
return 'Trabajo';
|
||||
case 'capital':
|
||||
return 'Capital';
|
||||
case 'emprendimiento':
|
||||
return 'Emprendimiento';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const getTipoColor = (tipo: string) => {
|
||||
switch (tipo) {
|
||||
case 'tierra':
|
||||
return 'bg-green-100 text-green-700 border-green-200';
|
||||
case 'trabajo':
|
||||
return 'bg-blue-100 text-blue-700 border-blue-200';
|
||||
case 'capital':
|
||||
return 'bg-amber-100 text-amber-700 border-amber-200';
|
||||
case 'emprendimiento':
|
||||
return 'bg-purple-100 text-purple-700 border-purple-200';
|
||||
default:
|
||||
return 'bg-gray-100 text-gray-700 border-gray-200';
|
||||
}
|
||||
};
|
||||
|
||||
const handleSeleccionar = (index: number) => {
|
||||
if (mostrarResultado) return;
|
||||
setRespuestaSeleccionada(index);
|
||||
};
|
||||
|
||||
const handleVerificar = () => {
|
||||
if (respuestaSeleccionada === null) return;
|
||||
|
||||
const esCorrecta = respuestaSeleccionada === pregunta.respuestaCorrecta;
|
||||
setMostrarResultado(true);
|
||||
|
||||
if (esCorrecta) {
|
||||
setPuntuacion(prev => prev + Math.round(100 / PREGUNTAS.length));
|
||||
setRespuestasCorrectas(prev => prev + 1);
|
||||
}
|
||||
|
||||
if (preguntaActual === PREGUNTAS.length - 1) {
|
||||
setTimeout(() => {
|
||||
setCompletado(true);
|
||||
const puntuacionFinal = puntuacion + (esCorrecta ? Math.round(100 / PREGUNTAS.length) : 0);
|
||||
if (onComplete) {
|
||||
onComplete(puntuacionFinal);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSiguiente = () => {
|
||||
setPreguntaActual(prev => prev + 1);
|
||||
setRespuestaSeleccionada(null);
|
||||
setMostrarResultado(false);
|
||||
};
|
||||
|
||||
const handleReiniciar = () => {
|
||||
setPreguntaActual(0);
|
||||
setRespuestaSeleccionada(null);
|
||||
setMostrarResultado(false);
|
||||
setPuntuacion(0);
|
||||
setCompletado(false);
|
||||
setRespuestasCorrectas(0);
|
||||
};
|
||||
|
||||
if (completado) {
|
||||
return (
|
||||
<Card className="w-full max-w-2xl mx-auto">
|
||||
<div className="text-center py-8 px-4">
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
className="inline-flex items-center justify-center w-20 h-20 bg-gradient-to-br from-yellow-400 to-yellow-600 rounded-full mb-6"
|
||||
>
|
||||
<Trophy size={40} className="text-white" />
|
||||
</motion.div>
|
||||
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-2">
|
||||
¡Quiz Completado!
|
||||
</h3>
|
||||
|
||||
<p className="text-gray-600 mb-6">
|
||||
Has respondido {respuestasCorrectas} de {PREGUNTAS.length} preguntas correctamente
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 max-w-md mx-auto mb-6">
|
||||
<div className={`p-4 rounded-xl border ${getTipoColor('tierra')}`}>
|
||||
<Mountain className="mx-auto mb-2" size={24} />
|
||||
<p className="font-semibold text-sm">Tierra</p>
|
||||
<p className="text-xs opacity-75">Rentas</p>
|
||||
</div>
|
||||
<div className={`p-4 rounded-xl border ${getTipoColor('trabajo')}`}>
|
||||
<Users className="mx-auto mb-2" size={24} />
|
||||
<p className="font-semibold text-sm">Trabajo</p>
|
||||
<p className="text-xs opacity-75">Salarios</p>
|
||||
</div>
|
||||
<div className={`p-4 rounded-xl border ${getTipoColor('capital')}`}>
|
||||
<Factory className="mx-auto mb-2" size={24} />
|
||||
<p className="font-semibold text-sm">Capital</p>
|
||||
<p className="text-xs opacity-75">Intereses</p>
|
||||
</div>
|
||||
<div className={`p-4 rounded-xl border ${getTipoColor('emprendimiento')}`}>
|
||||
<Lightbulb className="mx-auto mb-2" size={24} />
|
||||
<p className="font-semibold text-sm">Emprendimiento</p>
|
||||
<p className="text-xs opacity-75">Beneficios</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-blue-50 rounded-xl p-6 mb-6">
|
||||
<p className="text-sm text-blue-600 mb-1">Puntuación Total</p>
|
||||
<p className="text-4xl font-bold text-blue-700">{puntuacion}</p>
|
||||
<p className="text-sm text-blue-500">puntos</p>
|
||||
</div>
|
||||
|
||||
<Button onClick={handleReiniciar} variant="outline">
|
||||
<RotateCcw size={16} className="mr-2" />
|
||||
Intentar de Nuevo
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="w-full max-w-3xl mx-auto">
|
||||
<div className="p-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h3 className="text-xl font-bold text-gray-900">Factores de Producción</h3>
|
||||
<p className="text-sm text-gray-500">Pregunta {preguntaActual + 1} de {PREGUNTAS.length}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-xs text-gray-500">Puntos</p>
|
||||
<p className="text-xl font-bold text-blue-600">{puntuacion}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full bg-gray-200 rounded-full h-2 mb-6">
|
||||
<motion.div
|
||||
className="h-2 bg-blue-500 rounded-full"
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${((preguntaActual + 1) / PREGUNTAS.length) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-6">
|
||||
<div className={`inline-flex items-center gap-2 px-3 py-1 rounded-full text-xs font-semibold mb-3 border ${getTipoColor(pregunta.tipo)}`}>
|
||||
{getTipoIcon(pregunta.tipo)}
|
||||
<span>{getTipoLabel(pregunta.tipo)}</span>
|
||||
</div>
|
||||
<h4 className="text-lg font-medium text-gray-800">
|
||||
{pregunta.pregunta}
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 mb-6">
|
||||
{pregunta.opciones.map((opcion, index) => {
|
||||
const estaSeleccionada = respuestaSeleccionada === index;
|
||||
const esCorrecta = index === pregunta.respuestaCorrecta;
|
||||
const mostrarCorrecta = mostrarResultado && esCorrecta;
|
||||
const mostrarIncorrecta = mostrarResultado && estaSeleccionada && !esCorrecta;
|
||||
|
||||
return (
|
||||
<motion.button
|
||||
key={index}
|
||||
onClick={() => handleSeleccionar(index)}
|
||||
disabled={mostrarResultado}
|
||||
whileHover={!mostrarResultado ? { scale: 1.01 } : {}}
|
||||
whileTap={!mostrarResultado ? { scale: 0.99 } : {}}
|
||||
className={`w-full p-4 rounded-xl border-2 text-left transition-all ${
|
||||
mostrarCorrecta
|
||||
? 'border-green-500 bg-green-50'
|
||||
: mostrarIncorrecta
|
||||
? 'border-red-500 bg-red-50'
|
||||
: estaSeleccionada
|
||||
? 'border-blue-500 bg-blue-50'
|
||||
: 'border-gray-200 bg-white hover:border-blue-300'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-6 h-6 rounded-full border-2 flex items-center justify-center ${
|
||||
mostrarCorrecta
|
||||
? 'border-green-500 bg-green-500 text-white'
|
||||
: mostrarIncorrecta
|
||||
? 'border-red-500 bg-red-500 text-white'
|
||||
: estaSeleccionada
|
||||
? 'border-blue-500 bg-blue-500 text-white'
|
||||
: 'border-gray-300'
|
||||
}`}>
|
||||
{mostrarCorrecta && <CheckCircle size={14} />}
|
||||
{mostrarIncorrecta && <XCircle size={14} />}
|
||||
{!mostrarResultado && estaSeleccionada && (
|
||||
<div className="w-2 h-2 bg-white rounded-full" />
|
||||
)}
|
||||
</div>
|
||||
<span className={`font-medium ${
|
||||
mostrarCorrecta ? 'text-green-800' :
|
||||
mostrarIncorrecta ? 'text-red-800' :
|
||||
'text-gray-700'
|
||||
}`}>
|
||||
{opcion}
|
||||
</span>
|
||||
</div>
|
||||
</motion.button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<AnimatePresence>
|
||||
{mostrarResultado && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -10 }}
|
||||
className={`p-4 rounded-xl mb-6 ${
|
||||
respuestaSeleccionada === pregunta.respuestaCorrecta
|
||||
? 'bg-green-50 border border-green-200'
|
||||
: 'bg-red-50 border border-red-200'
|
||||
}`}
|
||||
>
|
||||
<p className={`font-medium mb-2 ${
|
||||
respuestaSeleccionada === pregunta.respuestaCorrecta
|
||||
? 'text-green-800'
|
||||
: 'text-red-800'
|
||||
}`}>
|
||||
{respuestaSeleccionada === pregunta.respuestaCorrecta
|
||||
? '¡Correcto!'
|
||||
: 'Incorrecto'}
|
||||
</p>
|
||||
<p className="text-sm text-gray-700">{pregunta.explicacion}</p>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<div className="flex justify-end">
|
||||
{!mostrarResultado ? (
|
||||
<Button
|
||||
onClick={handleVerificar}
|
||||
disabled={respuestaSeleccionada === null}
|
||||
>
|
||||
Verificar Respuesta
|
||||
</Button>
|
||||
) : preguntaActual < PREGUNTAS.length - 1 ? (
|
||||
<Button onClick={handleSiguiente}>
|
||||
Siguiente
|
||||
<ArrowRight size={16} className="ml-2" />
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default FactoresProduccionQuiz;
|
||||
Reference in New Issue
Block a user