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:
@@ -0,0 +1,278 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user