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:
577
frontend/src/components/exercises/modulo2/CambiosEquilibrio.tsx
Normal file
577
frontend/src/components/exercises/modulo2/CambiosEquilibrio.tsx
Normal file
@@ -0,0 +1,577 @@
|
||||
import React, { useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { GitBranch, ArrowRight, ArrowLeft, CheckCircle2, XCircle, Trophy, RotateCcw, BookOpen, TrendingUp, TrendingDown } from 'lucide-react';
|
||||
|
||||
interface CambiosEquilibrioProps {
|
||||
ejercicioId: string;
|
||||
onComplete?: (puntuacion: number) => void;
|
||||
}
|
||||
|
||||
type DireccionShock = 'oferta-aumenta' | 'oferta-disminuye' | 'demanda-aumenta' | 'demanda-disminuye';
|
||||
|
||||
interface Escenario {
|
||||
id: number;
|
||||
descripcion: string;
|
||||
shock: DireccionShock;
|
||||
curva: 'oferta' | 'demanda';
|
||||
direccion: 'aumenta' | 'disminuye';
|
||||
cambioPrecio: 'sube' | 'baja';
|
||||
cambioCantidad: 'sube' | 'baja';
|
||||
explicacion: string;
|
||||
dificultad: 'facil' | 'medio' | 'dificil';
|
||||
}
|
||||
|
||||
const escenarios: Escenario[] = [
|
||||
{
|
||||
id: 1,
|
||||
descripcion: 'Una nueva tecnología reduce los costos de producción de teléfonos inteligentes.',
|
||||
shock: 'oferta-aumenta',
|
||||
curva: 'oferta',
|
||||
direccion: 'aumenta',
|
||||
cambioPrecio: 'baja',
|
||||
cambioCantidad: 'sube',
|
||||
explicacion: 'La tecnología mejora la productividad, aumentando la oferta. La curva se desplaza a la derecha: el precio baja y la cantidad sube.',
|
||||
dificultad: 'facil'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
descripcion: 'Un informe de salud afirma que el café aumenta la longevidad.',
|
||||
shock: 'demanda-aumenta',
|
||||
curva: 'demanda',
|
||||
direccion: 'aumenta',
|
||||
cambioPrecio: 'sube',
|
||||
cambioCantidad: 'sube',
|
||||
explicacion: 'Las preferencias positivas aumentan la demanda. La curva se desplaza a la derecha: el precio y la cantidad suben.',
|
||||
dificultad: 'facil'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
descripcion: 'Una plaga de langostas destruye el 30% de la cosecha de granos.',
|
||||
shock: 'oferta-disminuye',
|
||||
curva: 'oferta',
|
||||
direccion: 'disminuye',
|
||||
cambioPrecio: 'sube',
|
||||
cambioCantidad: 'baja',
|
||||
explicacion: 'La plaga reduce la cantidad disponible, disminuyendo la oferta. La curva se desplaza a la izquierda: el precio sube y la cantidad baja.',
|
||||
dificultad: 'facil'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
descripcion: 'La economía entra en recesión y el ingreso promedio cae 20% (bien normal).',
|
||||
shock: 'demanda-disminuye',
|
||||
curva: 'demanda',
|
||||
direccion: 'disminuye',
|
||||
cambioPrecio: 'baja',
|
||||
cambioCantidad: 'baja',
|
||||
explicacion: 'Para bienes normales, al bajar el ingreso, disminuye la demanda. La curva se desplaza a la izquierda: el precio y la cantidad bajan.',
|
||||
dificultad: 'medio'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
descripcion: 'El gobierno subsidia la compra de autos eléctricos con $10,000.',
|
||||
shock: 'demanda-aumenta',
|
||||
curva: 'demanda',
|
||||
direccion: 'aumenta',
|
||||
cambioPrecio: 'sube',
|
||||
cambioCantidad: 'sube',
|
||||
explicacion: 'El subsidio reduce el precio efectivo para consumidores, aumentando la demanda. El equilibrio se mueve hacia mayor precio y cantidad.',
|
||||
dificultad: 'medio'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
descripcion: 'El precio del petróleo (insumo importante) sube un 50%.',
|
||||
shock: 'oferta-disminuye',
|
||||
curva: 'oferta',
|
||||
direccion: 'disminuye',
|
||||
cambioPrecio: 'sube',
|
||||
cambioCantidad: 'baja',
|
||||
explicacion: 'Al subir los costos de insumos, producir es más caro, disminuyendo la oferta. El equilibrio resulta en mayor precio y menor cantidad.',
|
||||
dificultad: 'dificil'
|
||||
}
|
||||
];
|
||||
|
||||
interface OpcionShock {
|
||||
value: DireccionShock;
|
||||
label: string;
|
||||
descripcion: string;
|
||||
icon: React.ReactNode;
|
||||
}
|
||||
|
||||
const opcionesShock: OpcionShock[] = [
|
||||
{ value: 'oferta-aumenta', label: 'Oferta ↑', descripcion: 'Aumenta', icon: <TrendingUp className="w-5 h-5" /> },
|
||||
{ value: 'oferta-disminuye', label: 'Oferta ↓', descripcion: 'Disminuye', icon: <TrendingDown className="w-5 h-5" /> },
|
||||
{ value: 'demanda-aumenta', label: 'Demanda ↑', descripcion: 'Aumenta', icon: <TrendingUp className="w-5 h-5" /> },
|
||||
{ value: 'demanda-disminuye', label: 'Demanda ↓', descripcion: 'Disminuye', icon: <TrendingDown className="w-5 h-5" /> },
|
||||
];
|
||||
|
||||
interface OpcionCambio {
|
||||
value: 'sube' | 'baja';
|
||||
label: string;
|
||||
icon: React.ReactNode;
|
||||
}
|
||||
|
||||
const opcionesCambio: OpcionCambio[] = [
|
||||
{ value: 'sube', label: 'Sube', icon: <TrendingUp className="w-5 h-5" /> },
|
||||
{ value: 'baja', label: 'Baja', icon: <TrendingDown className="w-5 h-5" /> },
|
||||
];
|
||||
|
||||
export const CambiosEquilibrio: React.FC<CambiosEquilibrioProps> = ({ onComplete, ejercicioId: _ejercicioId }) => {
|
||||
const [escenarioActual, setEscenarioActual] = useState(0);
|
||||
const [shockSeleccionado, setShockSeleccionado] = useState<DireccionShock | null>(null);
|
||||
const [cambioPrecio, setCambioPrecio] = useState<'sube' | 'baja' | null>(null);
|
||||
const [cambioCantidad, setCambioCantidad] = useState<'sube' | 'baja' | null>(null);
|
||||
const [mostrarResultado, setMostrarResultado] = useState(false);
|
||||
const [esCorrecto, setEsCorrecto] = useState(false);
|
||||
const [score, setScore] = useState(0);
|
||||
const [respuestasCorrectas, setRespuestasCorrectas] = useState(0);
|
||||
const [completado, setCompletado] = useState(false);
|
||||
const [_startTime] = useState(Date.now());
|
||||
|
||||
const escenario = escenarios[escenarioActual];
|
||||
|
||||
const handleVerificar = () => {
|
||||
if (!shockSeleccionado || !cambioPrecio || !cambioCantidad) return;
|
||||
|
||||
const shockCorrecto = shockSeleccionado === escenario.shock;
|
||||
const precioCorrecto = cambioPrecio === escenario.cambioPrecio;
|
||||
const cantidadCorrecta = cambioCantidad === escenario.cambioCantidad;
|
||||
|
||||
const todoCorrecto = shockCorrecto && precioCorrecto && cantidadCorrecta;
|
||||
|
||||
setEsCorrecto(todoCorrecto);
|
||||
setMostrarResultado(true);
|
||||
|
||||
if (todoCorrecto) {
|
||||
setScore(prev => prev + Math.round(100 / escenarios.length));
|
||||
setRespuestasCorrectas(prev => prev + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSiguiente = () => {
|
||||
if (escenarioActual < escenarios.length - 1) {
|
||||
setEscenarioActual(prev => prev + 1);
|
||||
setShockSeleccionado(null);
|
||||
setCambioPrecio(null);
|
||||
setCambioCantidad(null);
|
||||
setMostrarResultado(false);
|
||||
} else {
|
||||
setCompletado(true);
|
||||
if (onComplete) {
|
||||
onComplete(score);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleReiniciar = () => {
|
||||
setEscenarioActual(0);
|
||||
setShockSeleccionado(null);
|
||||
setCambioPrecio(null);
|
||||
setCambioCantidad(null);
|
||||
setMostrarResultado(false);
|
||||
setScore(0);
|
||||
setRespuestasCorrectas(0);
|
||||
setCompletado(false);
|
||||
};
|
||||
|
||||
const getDificultadColor = (dificultad: string) => {
|
||||
switch (dificultad) {
|
||||
case 'facil': return 'bg-green-100 text-green-700';
|
||||
case 'medio': return 'bg-yellow-100 text-yellow-700';
|
||||
case 'dificil': return 'bg-red-100 text-red-700';
|
||||
default: return 'bg-gray-100 text-gray-700';
|
||||
}
|
||||
};
|
||||
|
||||
const getShockColor = (value: DireccionShock) => {
|
||||
if (value.includes('oferta')) return value.includes('aumenta') ? 'green' : 'red';
|
||||
return value.includes('aumenta') ? 'blue' : 'orange';
|
||||
};
|
||||
|
||||
const renderGrafico = () => {
|
||||
const isOferta = escenario.curva === 'oferta';
|
||||
const isAumenta = escenario.direccion === 'aumenta';
|
||||
|
||||
return (
|
||||
<svg width="300" height="250" className="mx-auto">
|
||||
{/* Grid */}
|
||||
{Array.from({ length: 6 }).map((_, i) => (
|
||||
<g key={i}>
|
||||
<line x1={50 + i * 40} y1="30" x2={50 + i * 40} y2="210" stroke="#e5e7eb" strokeWidth="1" />
|
||||
<line x1="50" y1={30 + i * 36} x2="250" y2={30 + i * 36} stroke="#e5e7eb" strokeWidth="1" />
|
||||
</g>
|
||||
))}
|
||||
|
||||
{/* Ejes */}
|
||||
<line x1="50" y1="210" x2="250" y2="210" stroke="#374151" strokeWidth="2" />
|
||||
<line x1="50" y1="30" x2="50" y2="210" stroke="#374151" strokeWidth="2" />
|
||||
|
||||
{/* Labels */}
|
||||
<text x="150" y="235" textAnchor="middle" className="text-sm fill-gray-600">Q</text>
|
||||
<text x="25" y="120" textAnchor="middle" transform="rotate(-90, 25, 120)" className="text-sm fill-gray-600">P</text>
|
||||
|
||||
{/* Curva original */}
|
||||
{isOferta ? (
|
||||
<line x1="80" y1="180" x2="200" y2="80" stroke="#22c55e" strokeWidth="3" />
|
||||
) : (
|
||||
<line x1="80" y1="80" x2="200" y2="180" stroke="#3b82f6" strokeWidth="3" />
|
||||
)}
|
||||
<text x={isOferta ? 210 : 210} y={isOferta ? 75 : 190} className={`text-sm ${isOferta ? 'fill-green-600' : 'fill-blue-600'}`}>
|
||||
{isOferta ? 'S₁' : 'D₁'}
|
||||
</text>
|
||||
|
||||
{/* Curva desplazada */}
|
||||
{mostrarResultado && (
|
||||
<motion.g
|
||||
initial={{ opacity: 0, x: isAumenta ? 30 : -30 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
{isOferta ? (
|
||||
<line x1={isAumenta ? 110 : 50} y1="180" x2={isAumenta ? 230 : 170} y2="80" stroke="#22c55e" strokeWidth="3" strokeDasharray="5,5" />
|
||||
) : (
|
||||
<line x1={isAumenta ? 110 : 50} y1="80" x2={isAumenta ? 230 : 170} y2="180" stroke="#3b82f6" strokeWidth="3" strokeDasharray="5,5" />
|
||||
)}
|
||||
<text x={isAumenta ? 240 : 180} y={isOferta ? 75 : 190} className={`text-sm ${isOferta ? 'fill-green-600' : 'fill-blue-600'}`}>
|
||||
{isOferta ? 'S₂' : 'D₂'}
|
||||
</text>
|
||||
</motion.g>
|
||||
)}
|
||||
|
||||
{/* Punto de equilibrio original */}
|
||||
<circle cx="140" cy="130" r="5" fill="#8b5cf6" stroke="white" strokeWidth="2" />
|
||||
<text x="150" y="125" className="text-xs fill-purple-600">E₁</text>
|
||||
|
||||
{/* Nuevo equilibrio (si se muestra resultado) */}
|
||||
{mostrarResultado && (
|
||||
<motion.g
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.3, type: "spring" }}
|
||||
>
|
||||
<circle
|
||||
cx={isAumenta ? 170 : 110}
|
||||
cy={escenario.cambioPrecio === 'sube' ? 110 : 150}
|
||||
r="5"
|
||||
fill="#8b5cf6"
|
||||
stroke="white"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
<text x={isAumenta ? 180 : 120} y={escenario.cambioPrecio === 'sube' ? 105 : 145} className="text-xs fill-purple-600 font-bold">
|
||||
E₂
|
||||
</text>
|
||||
</motion.g>
|
||||
)}
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
if (completado) {
|
||||
const porcentaje = Math.round((respuestasCorrectas / escenarios.length) * 100);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
className="w-full max-w-2xl mx-auto p-8 bg-white rounded-xl shadow-lg text-center"
|
||||
>
|
||||
<Trophy className="w-16 h-16 text-yellow-500 mx-auto mb-4" />
|
||||
<h2 className="text-3xl font-bold text-gray-800 mb-2">¡Ejercicio Completado!</h2>
|
||||
<p className="text-gray-600 mb-6">Has analizado cambios en el equilibrio</p>
|
||||
|
||||
<div className="bg-gray-50 rounded-lg p-6 mb-6">
|
||||
<div className="text-5xl font-bold text-purple-600 mb-2">{porcentaje}%</div>
|
||||
<p className="text-gray-600">
|
||||
{respuestasCorrectas} de {escenarios.length} respuestas correctas
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleReiniciar}
|
||||
className="inline-flex items-center gap-2 px-6 py-3 bg-purple-600 text-white rounded-lg font-medium hover:bg-purple-700 transition-colors"
|
||||
>
|
||||
<RotateCcw className="w-5 h-5" />
|
||||
Intentar de nuevo
|
||||
</button>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto p-6 bg-white rounded-xl shadow-lg">
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<GitBranch className="w-8 h-8 text-purple-600" />
|
||||
<h2 className="text-2xl font-bold text-gray-800">Cambios en el Equilibrio</h2>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<span className={`px-3 py-1 rounded-full text-xs font-medium ${getDificultadColor(escenario.dificultad)}`}>
|
||||
{escenario.dificultad.toUpperCase()}
|
||||
</span>
|
||||
<span className="text-sm text-gray-500">
|
||||
{escenarioActual + 1} de {escenarios.length}
|
||||
</span>
|
||||
<div className="w-32 h-2 bg-gray-200 rounded-full overflow-hidden">
|
||||
<motion.div
|
||||
className="h-full bg-purple-600"
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${((escenarioActual + 1) / escenarios.length) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-gray-600">
|
||||
Analiza cómo los shocks del mercado afectan el precio y cantidad de equilibrio.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div className="space-y-4">
|
||||
<div className="bg-gradient-to-br from-purple-50 to-blue-50 rounded-lg p-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<BookOpen className="w-6 h-6 text-purple-600 flex-shrink-0 mt-1" />
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-800 mb-2">Escenario {escenario.id}</h3>
|
||||
<p className="text-gray-700 text-lg">{escenario.descripcion}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white border-2 border-gray-200 rounded-lg p-6">
|
||||
<h3 className="font-semibold text-gray-800 mb-4">1. ¿Qué curva se desplaza y en qué dirección?</h3>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{opcionesShock.map((opcion) => {
|
||||
const isSelected = shockSeleccionado === opcion.value;
|
||||
const isCorrect = mostrarResultado && opcion.value === escenario.shock;
|
||||
const color = getShockColor(opcion.value);
|
||||
|
||||
return (
|
||||
<motion.button
|
||||
key={opcion.value}
|
||||
onClick={() => !mostrarResultado && setShockSeleccionado(opcion.value)}
|
||||
disabled={mostrarResultado}
|
||||
whileHover={!mostrarResultado ? { scale: 1.02 } : {}}
|
||||
whileTap={!mostrarResultado ? { scale: 0.98 } : {}}
|
||||
className={`p-4 rounded-lg border-2 transition-all flex flex-col items-center gap-2 ${
|
||||
isCorrect
|
||||
? 'border-green-500 bg-green-50'
|
||||
: isSelected && mostrarResultado && opcion.value !== escenario.shock
|
||||
? 'border-red-500 bg-red-50'
|
||||
: isSelected
|
||||
? `border-${color}-500 bg-${color}-50`
|
||||
: 'border-gray-200 hover:border-gray-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
{opcion.icon}
|
||||
<span className={`font-semibold ${
|
||||
isCorrect ? 'text-green-700' :
|
||||
isSelected && mostrarResultado && opcion.value !== escenario.shock ? 'text-red-700' :
|
||||
isSelected ? `text-${color}-700` : 'text-gray-700'
|
||||
}`}>
|
||||
{opcion.label}
|
||||
</span>
|
||||
</motion.button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white border-2 border-gray-200 rounded-lg p-6">
|
||||
<h3 className="font-semibold text-gray-800 mb-4">2. ¿Cómo cambian el precio y la cantidad de equilibrio?</h3>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Precio (P*)</label>
|
||||
<div className="flex gap-2">
|
||||
{opcionesCambio.map((opcion) => {
|
||||
const isSelected = cambioPrecio === opcion.value;
|
||||
const isCorrect = mostrarResultado && opcion.value === escenario.cambioPrecio;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={opcion.value}
|
||||
onClick={() => !mostrarResultado && setCambioPrecio(opcion.value)}
|
||||
disabled={mostrarResultado}
|
||||
className={`flex-1 py-2 px-3 rounded-lg border-2 flex items-center justify-center gap-2 transition-all ${
|
||||
isCorrect
|
||||
? 'border-green-500 bg-green-50 text-green-700'
|
||||
: isSelected && mostrarResultado && opcion.value !== escenario.cambioPrecio
|
||||
? 'border-red-500 bg-red-50 text-red-700'
|
||||
: isSelected
|
||||
? 'border-purple-500 bg-purple-50 text-purple-700'
|
||||
: 'border-gray-200 hover:border-gray-300 text-gray-700'
|
||||
}`}
|
||||
>
|
||||
{opcion.icon}
|
||||
<span className="font-medium">{opcion.label}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Cantidad (Q*)</label>
|
||||
<div className="flex gap-2">
|
||||
{opcionesCambio.map((opcion) => {
|
||||
const isSelected = cambioCantidad === opcion.value;
|
||||
const isCorrect = mostrarResultado && opcion.value === escenario.cambioCantidad;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={opcion.value}
|
||||
onClick={() => !mostrarResultado && setCambioCantidad(opcion.value)}
|
||||
disabled={mostrarResultado}
|
||||
className={`flex-1 py-2 px-3 rounded-lg border-2 flex items-center justify-center gap-2 transition-all ${
|
||||
isCorrect
|
||||
? 'border-green-500 bg-green-50 text-green-700'
|
||||
: isSelected && mostrarResultado && opcion.value !== escenario.cambioCantidad
|
||||
? 'border-red-500 bg-red-50 text-red-700'
|
||||
: isSelected
|
||||
? 'border-purple-500 bg-purple-50 text-purple-700'
|
||||
: 'border-gray-200 hover:border-gray-300 text-gray-700'
|
||||
}`}
|
||||
>
|
||||
{opcion.icon}
|
||||
<span className="font-medium">{opcion.label}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AnimatePresence>
|
||||
{mostrarResultado && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className={`p-4 rounded-lg border ${esCorrecto ? 'bg-green-50 border-green-200' : 'bg-red-50 border-red-200'}`}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
{esCorrecto ? (
|
||||
<CheckCircle2 className="w-6 h-6 text-green-600 flex-shrink-0" />
|
||||
) : (
|
||||
<XCircle className="w-6 h-6 text-red-600 flex-shrink-0" />
|
||||
)}
|
||||
<div>
|
||||
<p className={`font-semibold ${esCorrecto ? 'text-green-800' : 'text-red-800'}`}>
|
||||
{esCorrecto ? '¡Correcto!' : 'Algunas respuestas son incorrectas'}
|
||||
</p>
|
||||
<p className="text-sm mt-1 text-gray-700">{escenario.explicacion}</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<div className="flex gap-3">
|
||||
{!mostrarResultado ? (
|
||||
<button
|
||||
onClick={handleVerificar}
|
||||
disabled={!shockSeleccionado || !cambioPrecio || !cambioCantidad}
|
||||
className="flex-1 py-3 px-4 bg-purple-600 text-white rounded-lg font-medium hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
Verificar Respuesta
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={handleSiguiente}
|
||||
className="flex-1 py-3 px-4 bg-green-600 text-white rounded-lg font-medium hover:bg-green-700 transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
{escenarioActual < escenarios.length - 1 ? (
|
||||
<>
|
||||
Siguiente
|
||||
<ArrowRight className="w-5 h-5" />
|
||||
</>
|
||||
) : (
|
||||
'Finalizar'
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 rounded-lg p-4">
|
||||
<h3 className="font-semibold text-gray-700 mb-4 text-center">Visualización del Cambio</h3>
|
||||
{renderGrafico()}
|
||||
|
||||
<div className="mt-4 p-3 bg-white rounded-lg">
|
||||
<h4 className="font-medium text-gray-700 mb-2">Resumen de efectos:</h4>
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex justify-between items-center p-2 bg-gray-50 rounded">
|
||||
<span className="text-gray-600">Curva de {escenario.curva}:</span>
|
||||
<span className={`font-medium ${escenario.direccion === 'aumenta' ? 'text-green-600' : 'text-red-600'}`}>
|
||||
Se {escenario.direccion === 'aumenta' ? 'desplaza a la derecha' : 'desplaza a la izquierda'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center p-2 bg-gray-50 rounded">
|
||||
<span className="text-gray-600">Precio de equilibrio:</span>
|
||||
<span className={`font-medium ${escenario.cambioPrecio === 'sube' ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{escenario.cambioPrecio === 'sube' ? '↑ Sube' : '↓ Baja'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center p-2 bg-gray-50 rounded">
|
||||
<span className="text-gray-600">Cantidad de equilibrio:</span>
|
||||
<span className={`font-medium ${escenario.cambioCantidad === 'sube' ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{escenario.cambioCantidad === 'sube' ? '↑ Sube' : '↓ Baja'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
|
||||
<p className="text-sm text-yellow-800">
|
||||
<strong>Recordatorio:</strong>
|
||||
</p>
|
||||
<ul className="text-sm text-yellow-700 mt-1 space-y-1">
|
||||
<li>• Oferta ↑ → P↓, Q↑</li>
|
||||
<li>• Oferta ↓ → P↑, Q↓</li>
|
||||
<li>• Demanda ↑ → P↑, Q↑</li>
|
||||
<li>• Demanda ↓ → P↓, Q↓</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex justify-between items-center">
|
||||
<button
|
||||
onClick={() => setEscenarioActual(Math.max(0, escenarioActual - 1))}
|
||||
disabled={escenarioActual === 0}
|
||||
className="flex items-center gap-2 px-4 py-2 text-gray-600 hover:text-gray-800 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
Anterior
|
||||
</button>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
{escenarios.map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
index === escenarioActual
|
||||
? 'bg-purple-600'
|
||||
: index < escenarioActual
|
||||
? 'bg-green-500'
|
||||
: 'bg-gray-300'
|
||||
}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setEscenarioActual(Math.min(escenarios.length - 1, escenarioActual + 1))}
|
||||
disabled={escenarioActual === escenarios.length - 1}
|
||||
className="flex items-center gap-2 px-4 py-2 text-gray-600 hover:text-gray-800 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
Siguiente
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CambiosEquilibrio;
|
||||
Reference in New Issue
Block a user