Files
econ/frontend/src/components/exercises/modulo3/CurvasIndiferencia.tsx
Renato aec6aef50f 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
2026-02-12 06:58:29 +01:00

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;