Files
econ/frontend/src/components/exercises/modulo4/IngresoCompetenciaPerfecta.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

279 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useMemo } from 'react';
import { Card, CardHeader } from '../../ui/Card';
import { Button } from '../../ui/Button';
import { Input } from '../../ui/Input';
import { CheckCircle, Scale, RotateCcw, TrendingUp } from 'lucide-react';
interface IngresoCompetenciaPerfectaProps {
ejercicioId: string;
onComplete?: (puntuacion: number) => void;
}
export function IngresoCompetenciaPerfecta({ ejercicioId: _ejercicioId, onComplete }: IngresoCompetenciaPerfectaProps) {
const PRECIO_MERCADO = 50;
const [cantidad, setCantidad] = useState(100);
const [respuestas, setRespuestas] = useState({
it: '',
img: '',
relacion: '',
});
const [validado, setValidado] = useState(false);
const [errores, setErrores] = useState<string[]>([]);
const ingresoTotal = useMemo(() => PRECIO_MERCADO * cantidad, [cantidad]);
const ingresoMarginal = PRECIO_MERCADO;
const ingresoPromedio = PRECIO_MERCADO;
const datosTabla = useMemo(() => {
const datos = [];
for (let q = 0; q <= 200; q += 25) {
datos.push({
q,
p: PRECIO_MERCADO,
it: PRECIO_MERCADO * q,
img: PRECIO_MERCADO,
ip: PRECIO_MERCADO,
});
}
return datos;
}, []);
const handleRespuestaChange = (campo: string, valor: string) => {
setRespuestas(prev => ({ ...prev, [campo]: valor }));
setValidado(false);
};
const validarRespuestas = () => {
const nuevosErrores: string[] = [];
if (parseFloat(respuestas.it) !== ingresoTotal) {
nuevosErrores.push(`IT incorrecto. IT = P × Q = ${PRECIO_MERCADO} × ${cantidad}`);
}
if (parseFloat(respuestas.img) !== ingresoMarginal) {
nuevosErrores.push(`IMg incorrecto. En competencia perfecta, IMg = P`);
}
if (!['igual', 'igual a', 'es igual', 'son iguales'].some(r => respuestas.relacion.toLowerCase().includes(r))) {
nuevosErrores.push('En competencia perfecta, P = IMg = IPMe');
}
setErrores(nuevosErrores);
setValidado(true);
if (nuevosErrores.length === 0 && onComplete) {
onComplete(100);
}
};
const reiniciar = () => {
setCantidad(100);
setRespuestas({ it: '', img: '', relacion: '' });
setValidado(false);
setErrores([]);
};
return (
<div className="space-y-6">
<Card>
<CardHeader
title="Ingreso en Competencia Perfecta"
subtitle="Precio = Ingreso Marginal = Ingreso Promedio"
/>
<div className="bg-green-50 p-4 rounded-lg mb-6">
<div className="flex items-center gap-2 mb-2">
<Scale className="w-5 h-5 text-green-600" />
<span className="font-semibold text-green-800">Características</span>
</div>
<p className="text-sm text-green-700">
En competencia perfecta, la empresa es tomadora de precios. El precio de mercado
es constante e independiente de la cantidad que produzca la empresa. Por eso:
<strong>P = IMg = IPMe</strong>. La curva de demanda es horizontal (perfectamente elástica).
</p>
</div>
<div className="grid md:grid-cols-3 gap-4 mb-6">
<div className="bg-blue-100 p-4 rounded-lg text-center border-2 border-blue-300">
<p className="text-sm text-blue-700 mb-1 font-medium">Precio de Mercado (P)</p>
<p className="text-3xl font-bold text-blue-800">${PRECIO_MERCADO}</p>
<p className="text-xs text-blue-600 mt-1">Constante</p>
</div>
<div className="bg-purple-100 p-4 rounded-lg text-center border-2 border-purple-300">
<p className="text-sm text-purple-700 mb-1 font-medium">Ingreso Marginal (IMg)</p>
<p className="text-3xl font-bold text-purple-800">${ingresoMarginal}</p>
<p className="text-xs text-purple-600 mt-1">=P</p>
</div>
<div className="bg-orange-100 p-4 rounded-lg text-center border-2 border-orange-300">
<p className="text-sm text-orange-700 mb-1 font-medium">Ingreso Promedio (IPMe)</p>
<p className="text-3xl font-bold text-orange-800">${ingresoPromedio}</p>
<p className="text-xs text-orange-600 mt-1">=P</p>
</div>
</div>
<div className="bg-gray-50 p-4 rounded-lg mb-6">
<label className="block text-sm font-medium text-gray-700 mb-2">
Cantidad producida (Q): {cantidad} unidades
</label>
<div className="flex items-center gap-4">
<input
type="range"
min="0"
max="200"
step="10"
value={cantidad}
onChange={(e) => {
setCantidad(parseInt(e.target.value));
setValidado(false);
}}
className="flex-1 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
/>
<span className="font-mono text-lg font-bold text-primary w-20 text-center">
{cantidad}
</span>
</div>
</div>
<div className="bg-green-50 border-2 border-green-200 p-6 rounded-lg mb-6">
<div className="text-center">
<p className="text-sm text-green-700 mb-2">Ingreso Total con Q = {cantidad}</p>
<p className="text-4xl font-bold text-green-800">
IT = ${ingresoTotal.toLocaleString()}
</p>
<p className="text-sm text-green-600 mt-2">
{PRECIO_MERCADO} × {cantidad} = ${ingresoTotal.toLocaleString()}
</p>
</div>
</div>
<div className="overflow-x-auto mb-6">
<table className="w-full text-sm">
<thead>
<tr className="bg-gray-50 border-b">
<th className="px-3 py-2 text-left font-medium text-gray-700">Q</th>
<th className="px-3 py-2 text-left font-medium text-gray-700">P ($)</th>
<th className="px-3 py-2 text-left font-medium text-gray-700">IT ($)</th>
<th className="px-3 py-2 text-left font-medium text-gray-700">IMg ($)</th>
<th className="px-3 py-2 text-left font-medium text-gray-700">IPMe ($)</th>
</tr>
</thead>
<tbody>
{datosTabla.filter((_, i) => i % 2 === 0 || i === datosTabla.length - 1).map((d, i) => (
<tr
key={i}
className={`border-b hover:bg-gray-50 ${d.q === cantidad ? 'bg-green-50' : ''}`}
>
<td className="px-3 py-2 font-medium">{d.q}</td>
<td className="px-3 py-2 text-blue-600 font-medium">${d.p}</td>
<td className="px-3 py-2">${d.it.toLocaleString()}</td>
<td className="px-3 py-2 text-purple-600">${d.img}</td>
<td className="px-3 py-2 text-orange-600">${d.ip}</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="bg-gradient-to-r from-blue-50 to-green-50 p-4 rounded-lg">
<h4 className="font-semibold text-gray-900 mb-4 flex items-center gap-2">
<TrendingUp className="w-5 h-5 text-blue-600" />
Responde para Q = {cantidad}:
</h4>
<div className="grid md:grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
¿Cuál es el IT? ($)
</label>
<Input
type="number"
value={respuestas.it}
onChange={(e) => handleRespuestaChange('it', e.target.value)}
className="w-full"
placeholder={String(PRECIO_MERCADO * cantidad)}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
¿Cuál es el IMg? ($)
</label>
<Input
type="number"
value={respuestas.img}
onChange={(e) => handleRespuestaChange('img', e.target.value)}
className="w-full"
placeholder="?"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
¿Cómo se relacionan P, IMg e IPMe?
</label>
<Input
type="text"
value={respuestas.relacion}
onChange={(e) => handleRespuestaChange('relacion', e.target.value)}
className="w-full"
placeholder="Son iguales / Diferentes"
/>
</div>
</div>
</div>
<div className="mt-4 flex gap-3">
<Button onClick={validarRespuestas} variant="primary">
<CheckCircle className="w-4 h-4 mr-2" />
Validar Respuestas
</Button>
<Button onClick={reiniciar} variant="outline">
<RotateCcw className="w-4 h-4 mr-2" />
Reiniciar
</Button>
</div>
{validado && errores.length === 0 && (
<div className="mt-4 p-4 bg-success/10 border border-success rounded-lg">
<div className="flex items-center gap-2 text-success">
<CheckCircle className="w-5 h-5" />
<span className="font-medium">¡Correcto! En competencia perfecta: P = IMg = IPMe = ${PRECIO_MERCADO}</span>
</div>
</div>
)}
{validado && errores.length > 0 && (
<div className="mt-4 p-4 bg-error/10 border border-error rounded-lg">
<p className="font-medium text-error mb-2">Revisa tus respuestas:</p>
<ul className="list-disc list-inside text-sm text-error">
{errores.map((error, i) => (
<li key={i}>{error}</li>
))}
</ul>
</div>
)}
</Card>
<Card className="bg-green-50 border-green-200">
<h4 className="font-semibold text-green-900 mb-2">Resumen - Competencia Perfecta:</h4>
<div className="grid md:grid-cols-2 gap-4 text-sm text-green-800">
<div>
<p className="font-medium mb-1">Fórmulas:</p>
<ul className="space-y-1">
<li> IT = P × Q</li>
<li> IMg = P (constante)</li>
<li> IPMe = P (constante)</li>
</ul>
</div>
<div>
<p className="font-medium mb-1">Características:</p>
<ul className="space-y-1">
<li> La empresa es tomadora de precios</li>
<li> Demanda horizontal (perfectamente elástica)</li>
<li> P = IMg = IPMe</li>
</ul>
</div>
</div>
</Card>
</div>
);
}
export default IngresoCompetenciaPerfecta;