Módulo 1 of 0
En Progreso
HERRAMIENTAS
Protectómetro DIMS–SIMS
// NOTE: This is a JavaScript (no TypeScript types) version of your component
const { useEffect, useMemo, useRef, useState } = React;
// --- Helpers ---
const uid = () => Math.random().toString(36).slice(2, 10);
const clamp = (v, min = 0, max = 10) => Math.max(min, Math.min(max, v));
const defaultPatient = () => ({
id: uid(),
name: "Paciente demo",
createdAt: new Date().toISOString(),
baseThreat: 5,
items: [
{ id: uid(), type: "SIM", label: "Dormir bien", weight: 1, category: "Biológico" },
{ id: uid(), type: "DIM", label: "Miedo a romperme", weight: 1.5, category: "Psicológico" },
{ id: uid(), type: "SIM", label: "Apoyo de amigos", weight: 0.75, category: "Social" },
],
});
const STORAGE_KEY = "protectometro_mvp_v1";
function App() {
const [patient, setPatient] = useState(() => {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (raw) return JSON.parse(raw);
} catch {}
return defaultPatient();
});
useEffect(() => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(patient));
}, [patient]);
const totals = useMemo(() => {
const sim = patient.items.filter(i => i.type === "SIM").reduce((a, b) => a + b.weight, 0);
const dim = patient.items.filter(i => i.type === "DIM").reduce((a, b) => a + b.weight, 0);
const threat = clamp(Number((patient.baseThreat + dim - sim).toFixed(2)));
return { sim, dim, net: dim - sim, threat };
}, [patient]);
const addItem = it => setPatient(p => ({ ...p, items: [{ id: uid(), ...it }, ...p.items] }));
const removeItem = id => setPatient(p => ({ ...p, items: p.items.filter(i => i.id !== id) }));
const clearAll = () => setPatient(p => ({ ...p, items: [] }));
const importRef = useRef(null);
const exportJSON = () => {
const blob = new Blob([JSON.stringify(patient, null, 2)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `protectometro_${patient.name.replace(/\s+/g, "_")}.json`;
a.click();
URL.revokeObjectURL(url);
};
const exportCSV = () => {
const header = ["type", "label", "weight", "category", "notes"].join(",");
const rows = patient.items.map(i => [
i.type,
`"${String(i.label).replace(/"/g, '""')}"`,
i.weight,
i.category,
`"${String(i.notes||"").replace(/"/g, '""')}"`
].join(","));
const csv = [header, ...rows].join("\n");
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `items_${patient.name.replace(/\s+/g, "_")}.csv`;
a.click();
URL.revokeObjectURL(url);
};
const importJSON = (file) => {
const reader = new FileReader();
reader.onload = () => {
try {
const data = JSON.parse(String(reader.result));
if (!data || !("items" in data)) throw new Error("Archivo inválido");
setPatient(data);
} catch (e) {
alert("No se pudo importar el archivo");
}
};
reader.readAsText(file);
};
return (
Protectómetro DIMS–SIMS
{const f=e.target.files?.[0]; if(f) importJSON(f);}} />
{/* Left: Controls & Gauge */}
Paciente
setPatient(p => ({ ...p, name: e.target.value }))}
/>
Amenaza basal
{patient.baseThreat.toFixed(1)}/10
setPatient(p => ({ ...p, baseThreat: Number(e.target.value) }))}
className="w-full"
/>
Estima clínica del punto de partida (contexto, irritabilidad, historia...).
Protectómetro
−{totals.sim.toFixed(2)}
SIMS
+{totals.dim.toFixed(2)}
DIMS
{totals.net.toFixed(2)}
Neto
{/* Middle: Add item */}
Añadir elemento
SIM
DIM
• Peso recomendado: 0.25–2 (ajústalo según impacto/creencia/contexto).
Lista de elementos
{patient.items.length === 0 ? (
Sin elementos todavía. Añade SIMS/DIMS arriba.
) : (
Tipo
Descripción
Peso
Categoría
Efecto
{patient.items.map(i => (
{i.type}
{i.label}
{i.weight}
{i.category}
{i.type === "SIM" ? `−${i.weight}` : `+${i.weight}`}
))}
)}
);
}
// --- Subcomponents ---
function Card({ children, className = "" }) {
return (
{children}
);
}
function Tag({ children, color = "gray" }) {
const map = {
emerald: "bg-emerald-50 text-emerald-700 border-emerald-200",
rose: "bg-rose-50 text-rose-700 border-rose-200",
gray: "bg-gray-50 text-gray-700 border-gray-200",
};
return {children};
}
function Gauge({ value }) {
const pct = (value / 10) * 100;
const gradient = `linear-gradient(90deg, #10b981 0%, #f59e0b 50%, #ef4444 100%)`;
return (
{value.toFixed(1)}
Amenaza (0–10)
0246810
);
}
function AddItemForm({ onAdd }) {
const [type, setType] = useState("SIM");
const [label, setLabel] = useState("");
const [weight, setWeight] = useState(1);
const [category, setCategory] = useState("Psicológico");
const [notes, setNotes] = useState("");
const submit = (e) => {
e.preventDefault();
if (!label.trim()) return;
onAdd({ type, label: label.trim(), weight: Number(weight), category, notes });
setLabel(""); setNotes("");
};
return (
setLabel(e.target.value)} />
setWeight(e.target.value)}>
{[0.25,0.5,0.75,1,1.25,1.5,1.75,2].map(v => {v})}
setCategory(e.target.value)}>
{["Biológico","Psicológico","Social","Otro"].map(v => {v})}
setNotes(e.target.value)} />
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render();
Protectómetro DIMS–SIMS
{const f=e.target.files?.[0]; if(f) importJSON(f);}} />
Paciente
setPatient(p => ({ ...p, name: e.target.value }))}
/>
Amenaza basal
{patient.baseThreat.toFixed(1)}/10
setPatient(p => ({ ...p, baseThreat: Number(e.target.value) }))}
className="w-full"
/>
Estima clínica del punto de partida (contexto, irritabilidad, historia...).
Protectómetro
−{totals.sim.toFixed(2)}
SIMS
+{totals.dim.toFixed(2)}
DIMS
{totals.net.toFixed(2)}
Neto
Añadir elemento
SIM
DIM
• Peso recomendado: 0.25–2 (ajústalo según impacto/creencia/contexto).
Lista de elementos
Sin elementos todavía. Añade SIMS/DIMS arriba.
) : (| Tipo | Descripción | Peso | Categoría | Efecto | |
|---|---|---|---|---|---|
| {i.type} | {i.label} | {i.weight} | {i.category} | {i.type === "SIM" ? `−${i.weight}` : `+${i.weight}`} |
{value.toFixed(1)}
Amenaza (0–10)
0246810
Responses