- 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
266 lines
9.5 KiB
TypeScript
266 lines
9.5 KiB
TypeScript
import { useState } from 'react';
|
||
import { Button } from '../../ui/Button';
|
||
import { Input } from '../../ui/Input';
|
||
import { Card, CardHeader } from '../../ui/Card';
|
||
|
||
interface Ejercicio {
|
||
id: number;
|
||
titulo: string;
|
||
descripcion: string;
|
||
i1: number;
|
||
i2: number;
|
||
q1: number;
|
||
q2: number;
|
||
unidadI: string;
|
||
unidadQ: string;
|
||
}
|
||
|
||
const ejercicios: Ejercicio[] = [
|
||
{
|
||
id: 1,
|
||
titulo: "Cálculo de Elasticidad Ingreso",
|
||
descripcion: "Cuando el ingreso mensual de una familia aumenta de $2,000 a $2,500, su consumo de carne aumenta de 8 kg a 12 kg mensuales.",
|
||
i1: 2000,
|
||
i2: 2500,
|
||
q1: 8,
|
||
q2: 12,
|
||
unidadI: "$/mes",
|
||
unidadQ: "kg"
|
||
},
|
||
{
|
||
id: 2,
|
||
titulo: "Elasticidad Ingreso - Producto Tecnológico",
|
||
descripcion: "El ingreso promedio de consumidores sube de $1,500 a $1,800 mensuales, y las ventas de smartphones premium aumentan de 50 a 80 unidades.",
|
||
i1: 1500,
|
||
i2: 1800,
|
||
q1: 50,
|
||
q2: 80,
|
||
unidadI: "$/mes",
|
||
unidadQ: "unidades"
|
||
},
|
||
{
|
||
id: 3,
|
||
titulo: "Elasticidad Ingreso - Transporte",
|
||
descripcion: "Cuando el ingreso familiar aumenta de $3,000 a $4,000 mensuales, el uso de transporte público disminuye de 40 a 25 viajes mensuales.",
|
||
i1: 3000,
|
||
i2: 4000,
|
||
q1: 40,
|
||
q2: 25,
|
||
unidadI: "$/mes",
|
||
unidadQ: "viajes"
|
||
}
|
||
];
|
||
|
||
interface Respuesta {
|
||
valor: string;
|
||
esCorrecta: boolean | null;
|
||
}
|
||
|
||
interface FormulaElasticidadIngresoProps {
|
||
ejercicioId: string;
|
||
onComplete?: (puntuacion: number) => void;
|
||
}
|
||
|
||
export function FormulaElasticidadIngreso({ ejercicioId: _ejercicioId, onComplete }: FormulaElasticidadIngresoProps) {
|
||
const [ejercicioActual, setEjercicioActual] = useState(0);
|
||
const [respuestas, setRespuestas] = useState<Record<number, Respuesta>>({});
|
||
const [mostrarSolucion, setMostrarSolucion] = useState<Record<number, boolean>>({});
|
||
const [mostrarFormula, setMostrarFormula] = useState(false);
|
||
|
||
const ejercicio = ejercicios[ejercicioActual];
|
||
|
||
const calcularElasticidad = (ej: Ejercicio) => {
|
||
const deltaQ = ej.q2 - ej.q1;
|
||
const deltaI = ej.i2 - ej.i1;
|
||
const qPromedio = (ej.q1 + ej.q2) / 2;
|
||
const iPromedio = (ej.i1 + ej.i2) / 2;
|
||
const porcentajeQ = (deltaQ / qPromedio) * 100;
|
||
const porcentajeI = (deltaI / iPromedio) * 100;
|
||
return porcentajeQ / porcentajeI;
|
||
};
|
||
|
||
const verificarRespuesta = () => {
|
||
const respuesta = respuestas[ejercicio.id];
|
||
if (!respuesta) return;
|
||
|
||
const valorCorrecto = calcularElasticidad(ejercicio);
|
||
const valorIngresado = parseFloat(respuesta.valor);
|
||
const esCorrecta = Math.abs(valorIngresado - valorCorrecto) <= 0.05;
|
||
|
||
setRespuestas(prev => ({
|
||
...prev,
|
||
[ejercicio.id]: { ...respuesta, esCorrecta }
|
||
}));
|
||
|
||
if (esCorrecta && ejercicioActual === ejercicios.length - 1 && onComplete) {
|
||
onComplete(100);
|
||
}
|
||
};
|
||
|
||
const handleRespuesta = (valor: string) => {
|
||
setRespuestas(prev => ({
|
||
...prev,
|
||
[ejercicio.id]: { valor, esCorrecta: null }
|
||
}));
|
||
};
|
||
|
||
const toggleSolucion = () => {
|
||
setMostrarSolucion(prev => ({
|
||
...prev,
|
||
[ejercicio.id]: !prev[ejercicio.id]
|
||
}));
|
||
};
|
||
|
||
const siguienteEjercicio = () => {
|
||
if (ejercicioActual < ejercicios.length - 1) {
|
||
setEjercicioActual(prev => prev + 1);
|
||
}
|
||
};
|
||
|
||
const resultado = calcularElasticidad(ejercicio);
|
||
const respuestaActual = respuestas[ejercicio.id];
|
||
|
||
return (
|
||
<Card className="max-w-3xl mx-auto">
|
||
<CardHeader
|
||
title="Fórmula de Elasticidad Ingreso"
|
||
subtitle={`Ejercicio ${ejercicioActual + 1} de ${ejercicios.length}`}
|
||
/>
|
||
|
||
<div className="mb-4">
|
||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||
<div
|
||
className="bg-primary h-2 rounded-full transition-all"
|
||
style={{ width: `${((ejercicioActual + 1) / ejercicios.length) * 100}%` }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-6">
|
||
<div className="bg-blue-50 p-4 rounded-lg">
|
||
<h3 className="font-bold text-lg text-blue-900">{ejercicio.titulo}</h3>
|
||
<p className="text-gray-700 mt-2">{ejercicio.descripcion}</p>
|
||
|
||
<div className="grid grid-cols-2 gap-4 mt-4">
|
||
<div className="bg-white p-3 rounded text-center border">
|
||
<span className="font-mono text-sm font-bold">I₁</span>
|
||
<p className="text-lg font-semibold">{ejercicio.i1.toLocaleString()} {ejercicio.unidadI}</p>
|
||
</div>
|
||
<div className="bg-white p-3 rounded text-center border">
|
||
<span className="font-mono text-sm font-bold">I₂</span>
|
||
<p className="text-lg font-semibold">{ejercicio.i2.toLocaleString()} {ejercicio.unidadI}</p>
|
||
</div>
|
||
<div className="bg-white p-3 rounded text-center border">
|
||
<span className="font-mono text-sm font-bold">Q₁</span>
|
||
<p className="text-lg font-semibold">{ejercicio.q1} {ejercicio.unidadQ}</p>
|
||
</div>
|
||
<div className="bg-white p-3 rounded text-center border">
|
||
<span className="font-mono text-sm font-bold">Q₂</span>
|
||
<p className="text-lg font-semibold">{ejercicio.q2} {ejercicio.unidadQ}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-gray-50 p-4 rounded-lg">
|
||
<div className="flex items-center justify-between mb-3">
|
||
<h4 className="font-semibold text-gray-700">Fórmula del método del punto medio:</h4>
|
||
<Button variant="ghost" size="sm" onClick={() => setMostrarFormula(!mostrarFormula)}>
|
||
{mostrarFormula ? 'Ocultar' : 'Mostrar'} fórmula
|
||
</Button>
|
||
</div>
|
||
|
||
{mostrarFormula && (
|
||
<div className="bg-white p-4 rounded border space-y-3">
|
||
<p className="font-mono text-center text-lg">
|
||
E<sub>i</sub> = (%ΔQ) / (%ΔI)
|
||
</p>
|
||
<div className="text-sm text-gray-600 space-y-1">
|
||
<p>Donde:</p>
|
||
<p>• %ΔQ = [(Q₂ - Q₁) / ((Q₁ + Q₂) / 2)] × 100</p>
|
||
<p>• %ΔI = [(I₂ - I₁) / ((I₁ + I₂) / 2)] × 100</p>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="border rounded-lg p-4">
|
||
<p className="text-gray-800 font-medium mb-3">
|
||
Calcule la elasticidad ingreso (E<sub>i</sub>):
|
||
</p>
|
||
|
||
<div className="flex gap-2">
|
||
<Input
|
||
type="number"
|
||
step="0.01"
|
||
value={respuestaActual?.valor || ''}
|
||
onChange={(e) => handleRespuesta(e.target.value)}
|
||
className="w-48"
|
||
placeholder="Respuesta"
|
||
/>
|
||
<Button
|
||
variant="outline"
|
||
onClick={verificarRespuesta}
|
||
disabled={!respuestaActual?.valor}
|
||
>
|
||
Verificar
|
||
</Button>
|
||
</div>
|
||
|
||
{respuestaActual?.esCorrecta !== null && (
|
||
<div className={`mt-3 p-3 rounded ${
|
||
respuestaActual.esCorrecta
|
||
? 'bg-green-100 text-green-800'
|
||
: 'bg-red-100 text-red-800'
|
||
}`}>
|
||
{respuestaActual.esCorrecta
|
||
? '¡Correcto!'
|
||
: 'Incorrecto. Revisa tus cálculos.'}
|
||
</div>
|
||
)}
|
||
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
onClick={toggleSolucion}
|
||
className="mt-2"
|
||
>
|
||
{mostrarSolucion[ejercicio.id] ? 'Ocultar' : 'Ver'} solución paso a paso
|
||
</Button>
|
||
|
||
{mostrarSolucion[ejercicio.id] && (
|
||
<div className="mt-2 bg-gray-50 p-4 rounded text-sm space-y-2">
|
||
<p className="font-semibold">Desarrollo:</p>
|
||
<p className="font-mono">ΔQ = {ejercicio.q2} - {ejercicio.q1} = {ejercicio.q2 - ejercicio.q1}</p>
|
||
<p className="font-mono">ΔI = {ejercicio.i2} - {ejercicio.i1} = {ejercicio.i2 - ejercicio.i1}</p>
|
||
<p className="font-mono">Q̄ = ({ejercicio.q1} + {ejercicio.q2}) / 2 = {((ejercicio.q1 + ejercicio.q2) / 2).toFixed(1)}</p>
|
||
<p className="font-mono">Ī = ({ejercicio.i1} + {ejercicio.i2}) / 2 = {((ejercicio.i1 + ejercicio.i2) / 2).toFixed(1)}</p>
|
||
<p className="font-mono">%ΔQ = ({ejercicio.q2 - ejercicio.q1} / {((ejercicio.q1 + ejercicio.q2) / 2).toFixed(1)}) × 100 = {(((ejercicio.q2 - ejercicio.q1) / ((ejercicio.q1 + ejercicio.q2) / 2)) * 100).toFixed(2)}%</p>
|
||
<p className="font-mono">%ΔI = ({ejercicio.i2 - ejercicio.i1} / {((ejercicio.i1 + ejercicio.i2) / 2).toFixed(1)}) × 100 = {(((ejercicio.i2 - ejercicio.i1) / ((ejercicio.i1 + ejercicio.i2) / 2)) * 100).toFixed(2)}%</p>
|
||
<p className="font-mono font-bold text-blue-800">
|
||
E<sub>i</sub> = {(((ejercicio.q2 - ejercicio.q1) / ((ejercicio.q1 + ejercicio.q2) / 2)) * 100).toFixed(2)} / {(((ejercicio.i2 - ejercicio.i1) / ((ejercicio.i1 + ejercicio.i2) / 2)) * 100).toFixed(2)} = {resultado.toFixed(2)}
|
||
</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="flex justify-end">
|
||
{ejercicioActual < ejercicios.length - 1 ? (
|
||
<Button
|
||
onClick={siguienteEjercicio}
|
||
disabled={respuestaActual?.esCorrecta !== true}
|
||
>
|
||
Siguiente Ejercicio
|
||
</Button>
|
||
) : (
|
||
<div className="text-green-600 font-semibold">
|
||
{respuestaActual?.esCorrecta ? '¡Ejercicios completados!' : ''}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
);
|
||
}
|
||
|
||
export default FormulaElasticidadIngreso;
|