import { useState, useMemo } from 'react'; import { Card, CardHeader } from '../../ui/Card'; import { Button } from '../../ui/Button'; import { Input } from '../../ui/Input'; import { CheckCircle, Target, TrendingUp, DollarSign } from 'lucide-react'; interface FilaProduccion { q: number; ct: number; } interface FilaCalculada { q: number; precio: number; it: number; ct: number; bt: number; img: number | null; cmg: number | null; } interface SimuladorProduccionProps { ejercicioId: string; onComplete?: (puntuacion: number) => void; } export function SimuladorProduccion({ ejercicioId: _ejercicioId, onComplete }: SimuladorProduccionProps) { const [precio, setPrecio] = useState(80); const datosBase: FilaProduccion[] = [ { q: 0, ct: 200 }, { q: 1, ct: 250 }, { q: 2, ct: 290 }, { q: 3, ct: 320 }, { q: 4, ct: 360 }, { q: 5, ct: 420 }, { q: 6, ct: 500 }, { q: 7, ct: 600 }, { q: 8, ct: 720 }, ]; const datosCalculados: FilaCalculada[] = useMemo(() => { return datosBase.map((fila, index) => { const it = precio * fila.q; const bt = it - fila.ct; const img = index > 0 ? precio : null; const cmg = index > 0 ? fila.ct - datosBase[index - 1].ct : null; return { q: fila.q, precio, it, ct: fila.ct, bt, img, cmg, }; }); }, [precio]); const qOptima = useMemo(() => { let maxBT = -Infinity; let qOpt = 0; datosCalculados.forEach((fila) => { if (fila.bt > maxBT) { maxBT = fila.bt; qOpt = fila.q; } }); return qOpt; }, [datosCalculados]); const verificacionIMgCMg = useMemo(() => { const filasValidas = datosCalculados.filter(f => f.img !== null && f.cmg !== null); const filaOptima = filasValidas.find(f => f.q === qOptima); if (!filaOptima) return null; return { img: filaOptima.img, cmg: filaOptima.cmg, diferencia: Math.abs((filaOptima.img || 0) - (filaOptima.cmg || 0)), cumple: Math.abs((filaOptima.img || 0) - (filaOptima.cmg || 0)) < 5, }; }, [datosCalculados, qOptima]); const maxValor = Math.max( ...datosCalculados.map(d => Math.max(d.it, d.ct, d.bt > 0 ? d.bt : 0)) ); const escala = maxValor > 0 ? 140 / maxValor : 1; const handleCompletar = () => { if (onComplete) { onComplete(100); } return 100; }; return (
setPrecio(parseFloat(e.target.value) || 0)} className="w-32" min="0" /> Ajusta el precio para ver cómo cambia la decisión óptima
{datosCalculados.map((fila) => ( ))}
Q Precio (P) IT = P × Q CT BT = IT - CT IMg CMg
{fila.q} {fila.precio} {fila.it} {fila.ct} = 0 ? 'text-success' : 'text-error'}`}> {fila.bt} {fila.img !== null ? fila.img : '-'} {fila.cmg !== null ? fila.cmg : '-'}

Cantidad Óptima: Q = {qOptima}

Beneficio Máximo: BT = {datosCalculados.find(d => d.q === qOptima)?.bt} {' '}(${precio} × {qOptima} - {datosCalculados.find(d => d.q === qOptima)?.ct})

{verificacionIMgCMg && (
{verificacionIMgCMg.cumple ? ( ) : ( )} Verificación IMg ≈ CMg:

IMg = {verificacionIMgCMg.img}, CMg = {verificacionIMgCMg.cmg} {' '}(Diferencia: {verificacionIMgCMg.diferencia.toFixed(1)})

{verificacionIMgCMg.cumple ? '✓ La condición de optimalidad se cumple: IMg ≈ CMg' : 'La diferencia es significativa, pero el beneficio sigue siendo máximo en Q = ' + qOptima}

)}
Cantidad (Q) $ {datosCalculados.map((d, i) => ( {d.q} ))} `${80 + i * 45},${190 - d.it * escala}`).join(' ')} /> `${80 + i * 45},${190 - d.ct * escala}`).join(' ')} /> {datosCalculados.map((d, i) => ( ))} d.q === qOptima) * 45}, ${ 190 - (datosCalculados.find(d => d.q === qOptima)?.it || 0) * escala - 20 })`}> Óptimo Q={qOptima} IT (Ingreso Total) CT (Costo Total)

Conceptos Clave

Ingreso Total (IT)

IT = P × Q

Beneficio Total (BT)

BT = IT - CT

Ingreso Marginal (IMg)

IMg = ΔIT / ΔQ = P (en competencia perfecta)

Condición de Optimalidad

IMg = CMg (producir hasta que el ingreso marginal iguale al costo marginal)

); } export default SimuladorProduccion;