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:
336
frontend/src/components/exercises/modulo3/CurvasIndiferencia.tsx
Normal file
336
frontend/src/components/exercises/modulo3/CurvasIndiferencia.tsx
Normal file
@@ -0,0 +1,336 @@
|
||||
import { useState } from 'react';
|
||||
import { Button } from '../../ui/Button';
|
||||
import { Card, CardHeader } from '../../ui/Card';
|
||||
|
||||
interface CurvasIndiferenciaProps {
|
||||
ejercicioId: string;
|
||||
onComplete?: (puntuacion: number) => void;
|
||||
}
|
||||
|
||||
interface Punto {
|
||||
x: number;
|
||||
y: number;
|
||||
utilidad: number;
|
||||
}
|
||||
|
||||
const curvas: Punto[][] = [
|
||||
// Curva U=10: 2x + 3y = 10
|
||||
[
|
||||
{ x: 0, y: 3.33, utilidad: 10 },
|
||||
{ x: 2, y: 2, utilidad: 10 },
|
||||
{ x: 5, y: 0, utilidad: 10 },
|
||||
],
|
||||
// Curva U=20: 2x + 3y = 20
|
||||
[
|
||||
{ x: 1, y: 6, utilidad: 20 },
|
||||
{ x: 4, y: 4, utilidad: 20 },
|
||||
{ x: 7, y: 2, utilidad: 20 },
|
||||
{ x: 10, y: 0, utilidad: 20 },
|
||||
],
|
||||
// Curva U=30: 2x + 3y = 30
|
||||
[
|
||||
{ x: 0, y: 10, utilidad: 30 },
|
||||
{ x: 3, y: 8, utilidad: 30 },
|
||||
{ x: 6, y: 6, utilidad: 30 },
|
||||
{ x: 9, y: 4, utilidad: 30 },
|
||||
{ x: 12, y: 2, utilidad: 30 },
|
||||
{ x: 15, y: 0, utilidad: 30 },
|
||||
],
|
||||
];
|
||||
|
||||
const puntosEjemplo = [
|
||||
{ x: 2, y: 2, label: 'A', utilidad: 10 },
|
||||
{ x: 4, y: 4, label: 'B', utilidad: 20 },
|
||||
{ x: 6, y: 6, label: 'C', utilidad: 30 },
|
||||
{ x: 3, y: 5, label: 'D', utilidad: 21 },
|
||||
{ x: 8, y: 2, label: 'E', utilidad: 22 },
|
||||
];
|
||||
|
||||
export function CurvasIndiferencia({ ejercicioId: _ejercicioId, onComplete }: CurvasIndiferenciaProps) {
|
||||
const [puntoSeleccionado, setPuntoSeleccionado] = useState<string | null>(null);
|
||||
const [mostrarPropiedades, setMostrarPropiedades] = useState(true);
|
||||
const [preguntaRespuesta, setPreguntaRespuesta] = useState<Record<string, string>>({});
|
||||
const [verificado, setVerificado] = useState(false);
|
||||
|
||||
const handleSeleccionPunto = (label: string) => {
|
||||
setPuntoSeleccionado(label);
|
||||
setVerificado(false);
|
||||
};
|
||||
|
||||
const verificarRespuesta = (pregunta: string, respuestaCorrecta: string) => {
|
||||
const esCorrecta = preguntaRespuesta[pregunta] === respuestaCorrecta;
|
||||
setVerificado(true);
|
||||
|
||||
if (esCorrecta && onComplete) {
|
||||
onComplete(100);
|
||||
}
|
||||
|
||||
return esCorrecta;
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="max-w-4xl mx-auto">
|
||||
<CardHeader
|
||||
title="Curvas de Indiferencia"
|
||||
subtitle="Mapa de preferencias que muestra combinaciones de bienes que proporcionan la misma utilidad"
|
||||
/>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="bg-blue-50 p-4 rounded-lg">
|
||||
<h3 className="font-bold text-blue-900 mb-2">Definición</h3>
|
||||
<p className="text-sm text-blue-800">
|
||||
Una <strong>curva de indiferencia</strong> muestra todas las combinaciones de dos bienes
|
||||
que proporcionan al consumidor el mismo nivel de utilidad o satisfacción.
|
||||
El consumidor es "indiferente" entre cualquiera de estas combinaciones.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="border rounded-lg p-4">
|
||||
<h4 className="font-bold mb-3">Mapa de Curvas de Indiferencia</h4>
|
||||
<div className="bg-white border rounded p-4">
|
||||
<div className="relative h-80">
|
||||
<svg viewBox="0 0 500 300" className="w-full h-full">
|
||||
<line x1="50" y1="280" x2="480" y2="280" stroke="#333" strokeWidth="2" />
|
||||
<line x1="50" y1="280" x2="50" y2="20" stroke="#333" strokeWidth="2" />
|
||||
|
||||
<text x="265" y="298" textAnchor="middle" className="text-sm fill-gray-700 font-bold">Bien X (Unidades)</text>
|
||||
<text x="15" y="150" textAnchor="middle" className="text-sm fill-gray-700 font-bold" transform="rotate(-90, 15, 150)">Bien Y (Unidades)</text>
|
||||
|
||||
{[0, 3, 6, 9, 12, 15].map((val) => (
|
||||
<g key={`x-${val}`}>
|
||||
<line x1={50 + val * 26} y1="280" x2={50 + val * 26} y2="285" stroke="#333" />
|
||||
<text x={50 + val * 26} y="298" textAnchor="middle" className="text-xs fill-gray-600">{val}</text>
|
||||
</g>
|
||||
))}
|
||||
|
||||
{[0, 2, 4, 6, 8, 10].map((val) => (
|
||||
<g key={`y-${val}`}>
|
||||
<line x1="45" y1={280 - val * 24} x2="50" y2={280 - val * 24} stroke="#333" />
|
||||
<text x="40" y={284 - val * 24} textAnchor="end" className="text-xs fill-gray-600">{val}</text>
|
||||
</g>
|
||||
))}
|
||||
|
||||
{curvas.map((curva, idx) => (
|
||||
<g key={idx}>
|
||||
<polyline
|
||||
points={curva.map(p => `${50 + p.x * 26},${280 - p.y * 24}`).join(' ')}
|
||||
fill="none"
|
||||
stroke={['#3b82f6', '#10b981', '#f59e0b'][idx]}
|
||||
strokeWidth="2"
|
||||
/>
|
||||
<text
|
||||
x={50 + curva[Math.floor(curva.length / 2)].x * 26 + 10}
|
||||
y={280 - curva[Math.floor(curva.length / 2)].y * 24}
|
||||
className="text-xs fill-gray-500"
|
||||
>
|
||||
U={curva[0].utilidad}
|
||||
</text>
|
||||
</g>
|
||||
))}
|
||||
|
||||
{puntosEjemplo.map((punto) => (
|
||||
<g key={punto.label}>
|
||||
<circle
|
||||
cx={50 + punto.x * 26}
|
||||
cy={280 - punto.y * 24}
|
||||
r={puntoSeleccionado === punto.label ? 8 : 6}
|
||||
fill={puntoSeleccionado === punto.label ? '#ef4444' : '#6366f1'}
|
||||
className="cursor-pointer"
|
||||
onClick={() => handleSeleccionPunto(punto.label)}
|
||||
/>
|
||||
<text
|
||||
x={50 + punto.x * 26 + 12}
|
||||
y={280 - punto.y * 24 - 5}
|
||||
className="text-sm font-bold fill-gray-700"
|
||||
>
|
||||
{punto.label}
|
||||
</text>
|
||||
</g>
|
||||
))}
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-gray-600 mt-2">
|
||||
Haz clic en los puntos (A, B, C, D, E) para ver sus características.
|
||||
Observa cómo las curvas más alejadas del origen representan mayor utilidad.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{puntoSeleccionado && (
|
||||
<div className="mt-4 bg-blue-50 p-4 rounded-lg">
|
||||
<h5 className="font-bold text-blue-900 mb-2">
|
||||
Punto {puntoSeleccionado}
|
||||
</h5>
|
||||
{(() => {
|
||||
const punto = puntosEjemplo.find(p => p.label === puntoSeleccionado);
|
||||
if (!punto) return null;
|
||||
return (
|
||||
<div className="space-y-1 text-sm text-blue-800">
|
||||
<p><strong>Bien X:</strong> {punto.x} unidades</p>
|
||||
<p><strong>Bien Y:</strong> {punto.y} unidades</p>
|
||||
<p><strong>Utilidad:</strong> {punto.utilidad} utils</p>
|
||||
<p className="text-xs mt-2">
|
||||
Este punto se encuentra en la curva de indiferencia U={punto.utilidad}.
|
||||
Cualquier otro punto en esta misma curva proporciona exactamente la misma satisfacción.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="bg-yellow-50 border border-yellow-300 p-4 rounded-lg">
|
||||
<h4 className="font-bold text-yellow-900 mb-3">Propiedades de las Curvas de Indiferencia</h4>
|
||||
<div className="space-y-3">
|
||||
<div className="flex gap-3">
|
||||
<div className="bg-white p-2 rounded w-8 h-8 flex items-center justify-center font-bold text-yellow-800 flex-shrink-0">1</div>
|
||||
<div>
|
||||
<p className="font-semibold text-sm">No se cortan</p>
|
||||
<p className="text-xs text-yellow-800">Dos curvas de indiferencia nunca pueden intersectarse. Si lo hicieran, implicaría que una misma combinación tiene dos niveles de utilidad diferentes.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<div className="bg-white p-2 rounded w-8 h-8 flex items-center justify-center font-bold text-yellow-800 flex-shrink-0">2</div>
|
||||
<div>
|
||||
<p className="font-semibold text-sm">Tienen pendiente negativa</p>
|
||||
<p className="text-xs text-yellow-800">Para mantener el mismo nivel de utilidad, si consumes más de un bien debes consumir menos del otro (sustitución).</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<div className="bg-white p-2 rounded w-8 h-8 flex items-center justify-center font-bold text-yellow-800 flex-shrink-0">3</div>
|
||||
<div>
|
||||
<p className="font-semibold text-sm">Son convexas al origen</p>
|
||||
<p className="text-xs text-yellow-800">La TMS (Tasa Marginal de Sustitución) disminuye a medida que te mueves hacia abajo a lo largo de la curva.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<div className="bg-white p-2 rounded w-8 h-8 flex items-center justify-center font-bold text-yellow-800 flex-shrink-0">4</div>
|
||||
<div>
|
||||
<p className="font-semibold text-sm">Curvas más alejadas = Mayor utilidad</p>
|
||||
<p className="text-xs text-yellow-800">Las curvas más alejadas del origen representan niveles de utilidad más altos (U=30 {'>'} U=20 {'>'} U=10).</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border rounded-lg p-4">
|
||||
<h4 className="font-bold mb-3">Tasa Marginal de Sustitución (TMS)</h4>
|
||||
<p className="text-sm text-gray-600 mb-3">
|
||||
La TMS mide cuántas unidades de Y estás dispuesto a sacrificar por una unidad adicional de X,
|
||||
manteniendo constante la utilidad.
|
||||
</p>
|
||||
<div className="bg-gray-50 p-3 rounded font-mono text-center text-sm mb-3">
|
||||
TMS = -ΔY/ΔX = UMgX / UMgY
|
||||
</div>
|
||||
<div className="bg-white border rounded p-3">
|
||||
<p className="font-semibold text-sm mb-2">Ejemplo en el punto A (2,2):</p>
|
||||
<div className="space-y-1 text-sm">
|
||||
<p>• Para aumentar X de 2 a 4 (ΔX = +2), debes reducir Y de 2 a... ¿cuánto?</p>
|
||||
<p>• En U=10: Si X=4, entonces 2(4) + 3Y = 10 → Y ≈ 0.67</p>
|
||||
<p>• TMS = -(0.67 - 2)/(4 - 2) = -(-1.33)/2 = 0.67</p>
|
||||
<p className="text-xs text-gray-600 mt-2">
|
||||
Estás dispuesto a dar up aproximadamente 0.67 unidades de Y por cada unidad adicional de X.
|
||||
</p> </div> </div> </div>
|
||||
|
||||
<div className="border-t pt-4">
|
||||
<h4 className="font-bold mb-4">Ejercicios de Comprensión</h4>
|
||||
<div className="space-y-4">
|
||||
<div className="border rounded-lg p-4">
|
||||
<p className="font-medium mb-3">1. ¿Qué significa que dos puntos estén en la misma curva de indiferencia?</p>
|
||||
<div className="space-y-2">
|
||||
{[
|
||||
{ id: '1a', texto: 'Tienen los mismos precios', correcta: false },
|
||||
{ id: '1b', texto: 'Proporcionan la misma utilidad', correcta: true },
|
||||
{ id: '1c', texto: 'Son igualmente caros', correcta: false },
|
||||
{ id: '1d', texto: 'Son bienes sustitutos perfectos', correcta: false },
|
||||
].map((opcion) => (
|
||||
<label key={opcion.id} className="flex items-center gap-2 p-2 hover:bg-gray-50 rounded cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
name="p1"
|
||||
onChange={() => setPreguntaRespuesta(prev => ({ ...prev, p1: opcion.id }))}
|
||||
className="text-primary"
|
||||
/>
|
||||
<span className="text-sm">{opcion.texto}</span>
|
||||
</label>
|
||||
))}
|
||||
</div> </div>
|
||||
|
||||
<div className="border rounded-lg p-4">
|
||||
<p className="font-medium mb-3">2. Según el mapa de curvas, ¿qué punto tiene mayor utilidad?</p>
|
||||
<div className="space-y-2">
|
||||
{[
|
||||
{ id: '2a', texto: 'Punto A (2,2)', correcta: false },
|
||||
{ id: '2b', texto: 'Punto B (4,4)', correcta: false },
|
||||
{ id: '2c', texto: 'Punto C (6,6)', correcta: true },
|
||||
{ id: '2d', texto: 'Punto D (3,5)', correcta: false },
|
||||
].map((opcion) => (
|
||||
<label key={opcion.id} className="flex items-center gap-2 p-2 hover:bg-gray-50 rounded cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
name="p2"
|
||||
onChange={() => setPreguntaRespuesta(prev => ({ ...prev, p2: opcion.id }))}
|
||||
className="text-primary"
|
||||
/>
|
||||
<span className="text-sm">{opcion.texto}</span>
|
||||
</label>
|
||||
))}
|
||||
</div> </div>
|
||||
|
||||
<div className="border rounded-lg p-4">
|
||||
<p className="font-medium mb-3">3. ¿Por qué las curvas de indiferencia tienen pendiente negativa?</p>
|
||||
<div className="space-y-2">
|
||||
{[
|
||||
{ id: '3a', texto: 'Porque los bienes son complementarios', correcta: false },
|
||||
{ id: '3b', texto: 'Para mantener la utilidad constante, más de X implica menos de Y', correcta: true },
|
||||
{ id: '3c', texto: 'Porque los precios son inversos', correcta: false },
|
||||
{ id: '3d', texto: 'Porque la utilidad marginal es negativa', correcta: false },
|
||||
].map((opcion) => (
|
||||
<label key={opcion.id} className="flex items-center gap-2 p-2 hover:bg-gray-50 rounded cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
name="p3"
|
||||
onChange={() => setPreguntaRespuesta(prev => ({ ...prev, p3: opcion.id }))}
|
||||
className="text-primary"
|
||||
/>
|
||||
<span className="text-sm">{opcion.texto}</span>
|
||||
</label>
|
||||
))}
|
||||
</div> </div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<Button
|
||||
onClick={() => {
|
||||
const correctas = [
|
||||
preguntaRespuesta.p1 === '1b',
|
||||
preguntaRespuesta.p2 === '2c',
|
||||
preguntaRespuesta.p3 === '3b'
|
||||
].filter(Boolean).length;
|
||||
setVerificado(true);
|
||||
if (correctas === 3 && onComplete) {
|
||||
onComplete(100);
|
||||
}
|
||||
}}
|
||||
disabled={!preguntaRespuesta.p1 || !preguntaRespuesta.p2 || !preguntaRespuesta.p3}
|
||||
>
|
||||
Verificar Respuestas
|
||||
</Button> </div>
|
||||
|
||||
{verificado && (
|
||||
<div className="mt-4 p-3 bg-green-100 border border-green-300 rounded-lg">
|
||||
<p className="text-green-800">
|
||||
¡Respuestas verificadas! Revisa cuáles fueron correctas.
|
||||
</p> </div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default CurvasIndiferencia;
|
||||
Reference in New Issue
Block a user