import React, { useState } from 'react';
import { Plus, Trash2, Calculator, ChefHat, Package, FileDown, RefreshCw, DollarSign, Percent, Box, Users, Wrench, Gift, Sparkles, ArrowRight, Check, Cake, Cookie, CakeSlice } from 'lucide-react';
// Importar fuente Nunito
const FontLoader = () => (
);
// Navegación
const NavButton = ({ active, onClick, icon: Icon, label }) => (
);
// Input con estilo
const StyledInput = ({ label, type = "text", value, onChange, placeholder, icon: Icon, suffix, min, step }) => (
);
};
// Fila de item editable
const ItemRow = ({ item, onUpdate, onDelete, fields }) => (
Reporte de Packaging y Mermas
`;
const blob = new Blob([content], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'reporte-packaging-mermas.html';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
return (
{/* Patrón decorativo */}
);
}
{label && }
);
// Select con estilo
const StyledSelect = ({ label, value, onChange, options, icon: Icon }) => (
{Icon && (
)}
{suffix && (
{suffix}
)}
{label && }
);
// Botón principal
const PrimaryButton = ({ onClick, children, variant = "primary", className = "", disabled }) => {
const variants = {
primary: "bg-gradient-to-r from-pink-400 to-rose-400 text-white hover:from-pink-500 hover:to-rose-500 shadow-lg shadow-pink-200/50",
secondary: "bg-white text-rose-400 border-2 border-pink-200 hover:border-pink-400 hover:bg-pink-50",
danger: "bg-gradient-to-r from-red-300 to-rose-300 text-white hover:from-red-400 hover:to-rose-400 shadow-lg shadow-red-200/50",
success: "bg-gradient-to-r from-emerald-300 to-teal-300 text-white hover:from-emerald-400 hover:to-teal-400 shadow-lg shadow-emerald-200/50"
};
return (
);
};
// Tarjeta de sección
const SectionCard = ({ title, icon: Icon, children, color = "pink" }) => {
const colors = {
pink: "from-pink-300 to-rose-300",
mint: "from-emerald-300 to-teal-300",
lavender: "from-violet-300 to-purple-300",
peach: "from-orange-200 to-amber-200",
sky: "from-sky-300 to-cyan-300"
};
return (
{Icon && (
)}
▼
{title}
{children}
{fields.map((field, i) => (
);
// ==================== PÁGINA 1: CALCULADORA DE PRECIOS ====================
const CalculadoraPrecios = () => {
const [config, setConfig] = useState({
nombre: '',
cantidad: 1,
unidad: 'unidad',
margen: 30
});
const [insumos, setInsumos] = useState([]);
const [manoObra, setManoObra] = useState([]);
const [costosIndirectos, setCostosIndirectos] = useState([]);
const [empaque, setEmpaque] = useState([]);
const [extras, setExtras] = useState([]);
const unidades = [
{ value: 'unidad', label: 'Unidad' },
{ value: 'kg', label: 'Kilogramo' },
{ value: 'litro', label: 'Litro' },
{ value: 'docena', label: 'Docena' },
{ value: 'caja', label: 'Caja' },
{ value: 'paquete', label: 'Paquete' }
];
const addItem = (setter, template) => {
setter(prev => [...prev, { id: Date.now(), ...template }]);
};
const updateItem = (setter, id, key, value) => {
setter(prev => prev.map(item => item.id === id ? { ...item, [key]: value } : item));
};
const deleteItem = (setter, id) => {
setter(prev => prev.filter(item => item.id !== id));
};
const calcularTotal = (items) => items.reduce((sum, item) => sum + (parseFloat(item.precio) || 0) * (parseFloat(item.cantidad) || 1), 0);
const totalInsumos = calcularTotal(insumos);
const totalManoObra = manoObra.reduce((sum, item) => sum + (parseFloat(item.costo) || 0), 0);
const totalIndirectos = costosIndirectos.reduce((sum, item) => sum + (parseFloat(item.costo) || 0), 0);
const totalEmpaque = calcularTotal(empaque);
const totalExtras = extras.reduce((sum, item) => sum + (parseFloat(item.costo) || 0), 0);
const costoTotal = totalInsumos + totalManoObra + totalIndirectos + totalEmpaque + totalExtras;
const costoPorUnidad = config.cantidad > 0 ? costoTotal / config.cantidad : 0;
const precioSugerido = costoPorUnidad * (1 + config.margen / 100);
const precioEspecial = precioSugerido * 0.9;
const gananciaUnidad = precioSugerido - costoPorUnidad;
const rentabilidad = costoPorUnidad > 0 ? (gananciaUnidad / precioSugerido) * 100 : 0;
return (
{field.type === 'select' ? (
) : (
onUpdate(field.key, e.target.value)}
placeholder={field.placeholder}
min={field.min}
step={field.step}
className="w-full px-3 py-2 bg-white border-2 border-pink-100 rounded-xl text-sm focus:border-pink-400 outline-none font-medium text-gray-600 placeholder:text-pink-200"
/>
)}
))}
{/* Configuración del Producto */}
setConfig({...config, nombre: e.target.value})}
placeholder="Ej: Torta de Chocolate"
/>
setConfig({...config, cantidad: e.target.value})}
min="1"
/>
setConfig({...config, unidad: e.target.value})}
options={unidades}
/>
setConfig({...config, margen: e.target.value})}
suffix="%"
min="0"
/>
{/* Resultados */}
);
};
// ==================== PÁGINA 2: MULTIPLICADOR DE RECETAS ====================
const MultiplicadorRecetas = () => {
const [receta, setReceta] = useState({
nombre: '',
moneda: 'USD'
});
const [ingredientes, setIngredientes] = useState([]);
const [factor, setFactor] = useState(1);
const monedas = [
{ value: 'USD', label: '$ USD' },
{ value: 'EUR', label: '€ EUR' },
{ value: 'MXN', label: '$ MXN' },
{ value: 'ARS', label: '$ ARS' },
{ value: 'COP', label: '$ COP' },
{ value: 'PEN', label: 'S/ PEN' }
];
const unidades = [
{ value: 'g', label: 'Gramos' },
{ value: 'kg', label: 'Kilogramos' },
{ value: 'ml', label: 'Mililitros' },
{ value: 'l', label: 'Litros' },
{ value: 'unidad', label: 'Unidad' },
{ value: 'taza', label: 'Taza' },
{ value: 'cda', label: 'Cucharada' },
{ value: 'cdta', label: 'Cucharadita' }
];
const simboloMoneda = monedas.find(m => m.value === receta.moneda)?.label.split(' ')[0] || '$';
const addIngrediente = () => {
setIngredientes([...ingredientes, { id: Date.now(), nombre: '', cantidad: '', unidad: 'g', precio: '' }]);
};
const updateIngrediente = (id, key, value) => {
setIngredientes(ingredientes.map(ing => ing.id === id ? { ...ing, [key]: value } : ing));
};
const deleteIngrediente = (id) => {
setIngredientes(ingredientes.filter(ing => ing.id !== id));
};
const calcularNuevaCantidad = (cantidad) => {
const num = parseFloat(cantidad) || 0;
return (num * factor).toFixed(2);
};
const calcularNuevoPrecio = (precio) => {
const num = parseFloat(precio) || 0;
return (num * factor).toFixed(2);
};
const costoOriginal = ingredientes.reduce((sum, ing) => sum + (parseFloat(ing.precio) || 0), 0);
const costoNuevo = costoOriginal * factor;
return (
{/* Insumos y Materia Prima */}
{/* Mano de Obra */}
{/* Costos Indirectos */}
{/* Empaque y Presentación */}
{/* Extras y Servicios */}
{insumos.map(item => (
updateItem(setInsumos, item.id, key, value)}
onDelete={() => deleteItem(setInsumos, item.id)}
fields={[
{ key: 'nombre', placeholder: 'Ingrediente', className: 'flex-[2] min-w-[120px]' },
{ key: 'cantidad', type: 'number', placeholder: 'Cant.', min: '0', step: '0.01', className: 'w-16 sm:w-20' },
{ key: 'unidad', type: 'select', options: unidades, className: 'w-24 sm:w-28' },
{ key: 'precio', type: 'number', placeholder: '$ Precio', min: '0', step: '0.01', className: 'w-20 sm:w-24' }
]}
/>
))}
addItem(setInsumos, { nombre: '', cantidad: 1, unidad: 'kg', precio: '' })} className="w-full sm:w-auto">
Agregar Insumo
Subtotal: ${totalInsumos.toFixed(2)}
{manoObra.map(item => (
updateItem(setManoObra, item.id, key, value)}
onDelete={() => deleteItem(setManoObra, item.id)}
fields={[
{ key: 'descripcion', placeholder: 'Descripción', className: 'flex-[2] min-w-[120px]' },
{ key: 'horas', type: 'number', placeholder: 'Horas', min: '0', step: '0.5', className: 'w-16 sm:w-20' },
{ key: 'costo', type: 'number', placeholder: '$ Costo', min: '0', step: '0.01', className: 'w-20 sm:w-24' }
]}
/>
))}
addItem(setManoObra, { descripcion: '', horas: 1, costo: '' })} className="w-full sm:w-auto">
Agregar Mano de Obra
Subtotal: ${totalManoObra.toFixed(2)}
{costosIndirectos.map(item => (
updateItem(setCostosIndirectos, item.id, key, value)}
onDelete={() => deleteItem(setCostosIndirectos, item.id)}
fields={[
{ key: 'concepto', placeholder: 'Concepto (luz, gas, etc.)', className: 'flex-[2] min-w-[120px]' },
{ key: 'costo', type: 'number', placeholder: '$ Costo', min: '0', step: '0.01', className: 'w-24 sm:w-28' }
]}
/>
))}
addItem(setCostosIndirectos, { concepto: '', costo: '' })} className="w-full sm:w-auto">
Agregar Costo Indirecto
Subtotal: ${totalIndirectos.toFixed(2)}
{empaque.map(item => (
updateItem(setEmpaque, item.id, key, value)}
onDelete={() => deleteItem(setEmpaque, item.id)}
fields={[
{ key: 'item', placeholder: 'Caja, etiqueta, etc.', className: 'flex-[2] min-w-[120px]' },
{ key: 'cantidad', type: 'number', placeholder: 'Cant.', min: '0', className: 'w-16 sm:w-20' },
{ key: 'precio', type: 'number', placeholder: '$ Precio', min: '0', step: '0.01', className: 'w-20 sm:w-24' }
]}
/>
))}
addItem(setEmpaque, { item: '', cantidad: 1, precio: '' })} className="w-full sm:w-auto">
Agregar Empaque
Subtotal: ${totalEmpaque.toFixed(2)}
{extras.map(item => (
updateItem(setExtras, item.id, key, value)}
onDelete={() => deleteItem(setExtras, item.id)}
fields={[
{ key: 'servicio', placeholder: 'Delivery, decoración, etc.', className: 'flex-[2] min-w-[120px]' },
{ key: 'costo', type: 'number', placeholder: '$ Costo', min: '0', step: '0.01', className: 'w-24 sm:w-28' }
]}
/>
))}
addItem(setExtras, { servicio: '', costo: '' })} className="w-full sm:w-auto">
Agregar Extra
Subtotal: ${totalExtras.toFixed(2)}
Resumen de Costos
Insumos y Materia Prima
${totalInsumos.toFixed(2)}
Mano de Obra
${totalManoObra.toFixed(2)}
Costos Indirectos
${totalIndirectos.toFixed(2)}
Empaque y Presentación
${totalEmpaque.toFixed(2)}
Extras y Servicios
${totalExtras.toFixed(2)}
COSTO TOTAL
${costoTotal.toFixed(2)}
Costo por {config.unidad}
${costoPorUnidad.toFixed(2)}
💰 Precio Sugerido (con {config.margen}% margen)
${precioSugerido.toFixed(2)}
por {config.unidad}
🏷️ Precio Especial (-10%)
${precioEspecial.toFixed(2)}
por {config.unidad}
Análisis de Rentabilidad
Ganancia por unidad
${gananciaUnidad.toFixed(2)}
Margen de rentabilidad
{rentabilidad.toFixed(1)}%
{/* Configuración de Receta */}
setReceta({...receta, nombre: e.target.value})}
placeholder="Ej: Pastel de Tres Leches"
icon={ChefHat}
/>
setReceta({...receta, moneda: e.target.value})}
options={monedas}
icon={DollarSign}
/>
{/* Ingredientes */}
{/* Factor de Multiplicación */}
{/* Receta Final */}
{ingredientes.length > 0 && (
)}
);
};
// ==================== PÁGINA 3: PACKAGING Y MERMAS ====================
const PackagingMermas = () => {
const [envases, setEnvases] = useState([]);
const [resultados, setResultados] = useState(null);
const tiposEnvase = [
{ value: 'caja_carton', label: '📦 Caja de Cartón' },
{ value: 'bolsa_papel', label: '🛍️ Bolsa de Papel' },
{ value: 'envase_plastico', label: '🥤 Envase Plástico' },
{ value: 'frasco_vidrio', label: '🫙 Frasco de Vidrio' },
{ value: 'bandeja_aluminio', label: '🍱 Bandeja de Aluminio' },
{ value: 'film_transparente', label: '🎞️ Film Transparente' },
{ value: 'etiqueta', label: '🏷️ Etiqueta' },
{ value: 'cinta_decorativa', label: '🎀 Cinta Decorativa' },
{ value: 'otro', label: '📋 Otro' }
];
const addEnvase = () => {
setEnvases([...envases, {
id: Date.now(),
tipo: 'caja_carton',
precioUnidad: '',
cantidad: 1,
merma: 5
}]);
};
const updateEnvase = (id, key, value) => {
setEnvases(envases.map(env => env.id === id ? { ...env, [key]: value } : env));
};
const deleteEnvase = (id) => {
setEnvases(envases.filter(env => env.id !== id));
};
const calcularCostos = () => {
const detalle = envases.map(env => {
const precioBase = (parseFloat(env.precioUnidad) || 0) * (parseInt(env.cantidad) || 1);
const costoMerma = precioBase * ((parseFloat(env.merma) || 0) / 100);
const costoReal = precioBase + costoMerma;
return {
...env,
tipoLabel: tiposEnvase.find(t => t.value === env.tipo)?.label || env.tipo,
precioBase,
costoMerma,
costoReal
};
});
const totalBase = detalle.reduce((sum, d) => sum + d.precioBase, 0);
const totalMerma = detalle.reduce((sum, d) => sum + d.costoMerma, 0);
const totalReal = detalle.reduce((sum, d) => sum + d.costoReal, 0);
setResultados({ detalle, totalBase, totalMerma, totalReal });
};
const reiniciar = () => {
setEnvases([]);
setResultados(null);
};
const exportarPDF = () => {
const content = `
{ingredientes.map(ing => (
Añadir Ingrediente
updateIngrediente(ing.id, 'nombre', e.target.value)}
placeholder="Ingrediente"
className="flex-[2] min-w-[100px] px-3 py-2 bg-white border-2 border-emerald-100 rounded-xl text-sm focus:border-emerald-400 outline-none font-medium text-gray-600 placeholder:text-emerald-200"
/>
updateIngrediente(ing.id, 'cantidad', e.target.value)}
placeholder="Cantidad"
min="0"
step="0.01"
className="w-20 sm:w-24 px-3 py-2 bg-white border-2 border-emerald-100 rounded-xl text-sm focus:border-emerald-400 outline-none font-medium text-gray-600 placeholder:text-emerald-200"
/>
))}
{simboloMoneda}
updateIngrediente(ing.id, 'precio', e.target.value)}
placeholder="Precio"
min="0"
step="0.01"
className="w-full pl-8 pr-3 py-2 bg-white border-2 border-emerald-100 rounded-xl text-sm focus:border-emerald-400 outline-none font-medium text-gray-600 placeholder:text-emerald-200"
/>
Factor de Multiplicación
{[0.5, 1, 1.5, 2, 3, 4, 5].map(f => (
))}
Personalizado:
setFactor(parseFloat(e.target.value) || 1)}
min="0.1"
step="0.1"
className="w-20 sm:w-24 px-3 py-2 bg-white/80 border-2 border-violet-300 rounded-xl text-violet-600 font-bold text-center focus:border-violet-500 outline-none"
/>
| Ingrediente | Original |
|
Nueva Cantidad | Nuevo Precio |
|---|---|---|---|---|
| {ing.nombre || '—'} | {ing.cantidad} {unidades.find(u => u.value === ing.unidad)?.label} |
|
{calcularNuevaCantidad(ing.cantidad)} {unidades.find(u => u.value === ing.unidad)?.label} | {simboloMoneda}{calcularNuevoPrecio(ing.precio)} |
| COSTO TOTAL | {simboloMoneda}{costoOriginal.toFixed(2)} | {simboloMoneda}{costoNuevo.toFixed(2)} | ||
🧁 Reporte de Packaging y Mermas
Fecha: ${new Date().toLocaleDateString()}
📦 Detalle de Envases
| Tipo de Envase | Cantidad | Precio Unit. | % Merma | Costo Base | Costo Merma | Costo Real |
|---|---|---|---|---|---|---|
| ${d.tipoLabel} | ${d.cantidad} | $${parseFloat(d.precioUnidad).toFixed(2)} | ${d.merma}% | $${d.precioBase.toFixed(2)} | $${d.costoMerma.toFixed(2)} | $${d.costoReal.toFixed(2)} |
| TOTALES | $${resultados?.totalBase.toFixed(2) || '0.00'} | $${resultados?.totalMerma.toFixed(2) || '0.00'} | $${resultados?.totalReal.toFixed(2) || '0.00'} | |||
📊 Resumen
Costo Base Total: $${resultados?.totalBase.toFixed(2) || '0.00'}
Costo por Mermas: $${resultados?.totalMerma.toFixed(2) || '0.00'}
Costo Real Total: $${resultados?.totalReal.toFixed(2) || '0.00'}
{/* Configuración de Envases */}
{/* Botones de Acción */}
Calcular Costo Real
Reiniciar
{/* Resultados */}
{resultados && (
<>
{/* Indicadores */}
{/* Exportar PDF */}
Exportar Resultados (HTML)
>
)}
);
};
// ==================== APP PRINCIPAL ====================
export default function App() {
const [pagina, setPagina] = useState('calculadora');
return (
{envases.map(env => (
))}
Agregar Envase
updateEnvase(env.id, 'cantidad', e.target.value)}
min="1"
className="w-full px-3 py-2.5 bg-white border-2 border-sky-200 rounded-xl text-center font-bold focus:border-sky-400 outline-none text-gray-600 text-sm"
/>
$
updateEnvase(env.id, 'precioUnidad', e.target.value)}
placeholder="0.00"
min="0"
step="0.01"
className="w-full pl-7 pr-3 py-2.5 bg-white border-2 border-sky-200 rounded-xl font-bold focus:border-sky-400 outline-none text-gray-600 text-sm"
/>
updateEnvase(env.id, 'merma', e.target.value)}
min="0"
max="100"
className="w-full px-3 py-2.5 bg-white border-2 border-sky-200 rounded-xl text-center font-bold focus:border-sky-400 outline-none text-gray-600 text-sm"
/>
%
| Envase | Cant. | Costo Base | + Merma | Costo Real |
|---|---|---|---|---|
| {d.tipoLabel} | {d.cantidad} | ${d.precioBase.toFixed(2)} | +${d.costoMerma.toFixed(2)} | ${d.costoReal.toFixed(2)} |
💵 Costo Base
${resultados.totalBase.toFixed(2)}
📉 Costo Mermas
+${resultados.totalMerma.toFixed(2)}
✨ Costo Real Total
${resultados.totalReal.toFixed(2)}
{/* Decoraciones de repostería */}
🧁
🍰
🍪
🎂
{/* Header */}
{/* Navegación */}
{/* Contenido */}
{pagina === 'calculadora' && }
{pagina === 'recetas' && }
{pagina === 'packaging' && }
{/* Footer */}
🧁
Calculadora de Repostería
🍰✨ Gestiona costos, recetas y packaging de manera dulce y profesional ✨