// CustomCalc — let the user build their own simple calculator. // Variables + formula + optional bands → saved in localStorage. const { useState: uxS, useMemo: uxM, useEffect: uxE } = React; const CC_KEY = 'cyna_custom_calcs_v1'; const newCalc = () => ({ id: 'c_' + Date.now().toString(36), nombre: 'Mi calculadora', descripcion: '', unidad: '', formula: 'peso * 0.5', variables: [ { id: 'peso', label: 'Peso', unit: 'kg', value: 70 }, ], bandas: [ // { hasta: Infinity, label: 'Normal', color: '#6E8A4C' } ], }); function loadCalcs() { try { const raw = localStorage.getItem(CC_KEY); if (raw) return JSON.parse(raw); } catch {} return [ { id: 'demo1', nombre: 'Dosis por kilo', descripcion: 'Dosis total a administrar según peso y dosis por kg.', unidad: 'mg', formula: 'peso * dosis', variables: [ { id: 'peso', label: 'Peso', unit: 'kg', value: 70 }, { id: 'dosis', label: 'Dosis por kg', unit: 'mg/kg', value: 10 }, ], bandas: [], }, ]; } function saveCalcs(arr) { try { localStorage.setItem(CC_KEY, JSON.stringify(arr)); } catch {} } // Safe-ish evaluator: allow only digits, operators, parens, dots, spaces and known variable ids. function compileFormula(formula, varIds) { const idSet = new Set(varIds); // Tokenize: numbers, identifiers, operators, parens. const tokens = formula.match(/[A-Za-z_][A-Za-z0-9_]*|\d+\.?\d*|[+\-*/().%,]|\*\*|\s+/g) || []; for (const t of tokens) { if (/^\s+$/.test(t)) continue; if (/^[A-Za-z_]/.test(t)) { if (!idSet.has(t) && !['Math', 'min', 'max', 'sqrt', 'pow', 'abs', 'round', 'floor', 'ceil', 'log'].includes(t)) { return { error: `Variable desconocida: ${t}` }; } } else if (!/^(\d+\.?\d*|[+\-*/().%,]|\*\*)$/.test(t)) { return { error: `Token no permitido: ${t}` }; } } try { // eslint-disable-next-line no-new-func const fn = new Function(...varIds, 'Math', 'min', 'max', 'sqrt', 'pow', 'abs', 'round', 'floor', 'ceil', 'log', `"use strict"; return (${formula});`); return { fn }; } catch (e) { return { error: 'Sintaxis inválida' }; } } function evalCalc(calc) { const ids = calc.variables.map(v => v.id); const { fn, error } = compileFormula(calc.formula, ids); if (error) return { error }; try { const args = calc.variables.map(v => +v.value || 0); const r = fn(...args, Math, Math.min, Math.max, Math.sqrt, Math.pow, Math.abs, Math.round, Math.floor, Math.ceil, Math.log); return { value: r }; } catch (e) { return { error: 'No se pudo calcular' }; } } function slug(s) { return s.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '') .replace(/[^a-z0-9_]+/g, '_').replace(/^_+|_+$/g, '') || 'var'; } function CustomCalc() { const [list, setList] = uxS(loadCalcs); const [activeId, setActiveId] = uxS(() => loadCalcs()[0]?.id || null); const [editing, setEditing] = uxS(false); uxE(() => { saveCalcs(list); }, [list]); const active = list.find(c => c.id === activeId) || list[0]; const update = (patch) => setList(list.map(c => c.id === active.id ? { ...c, ...patch } : c)); const remove = (id) => { if (!confirm('¿Eliminar esta calculadora?')) return; const next = list.filter(c => c.id !== id); setList(next); if (next.length) setActiveId(next[0].id); }; const create = () => { const c = newCalc(); const next = [...list, c]; setList(next); setActiveId(c.id); setEditing(true); }; if (!active) { return (
Crea la primera con tus propias variables y fórmula.
{calc.descripcion}
}Fórmula: {calc.formula}
Configura los campos que el usuario verá y la fórmula a aplicar.
Sin bandas. Añade rangos para clasificar el resultado (p. ej. "Normal: hasta 24.9").
)}