- 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
337 lines
15 KiB
TypeScript
337 lines
15 KiB
TypeScript
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;
|