/* Estils per a la graella de fons */ .grid-bg { background-image: radial-gradient(#e2e8f0 1px, transparent 1px); background-size: 30px 30px; } .node-pulse { animation: pulse 2s infinite; } @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(99, 102, 241, 0.4); } 70% { box-shadow: 0 0 0 10px rgba(99, 102, 241, 0); } 100% { box-shadow: 0 0 0 0 rgba(99, 102, 241, 0); } } .custom-scrollbar::-webkit-scrollbar { width: 6px; } .custom-scrollbar::-webkit-scrollbar-track { background: #f1f1f1; } .custom-scrollbar::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; } /* WordPress Compatibility */ .wp-sociograma { line-height: 1.5; color: #1e293b; } /* Estats de selecció */ .connecting-mode .student-node { cursor: cell !important; } .connecting-mode .student-card { cursor: cell !important; border-color: #6366f1 !important; background-color: #eef2ff !important; }

Mapa de Convivència Escolar

Eina de diagnòstic i intervenció per a l'aula

Llegenda de Rols

Agressor
Víctima
Defensor
Seguidor
Espectador
Sense Rol
// --- DADES I CONFIGURACIÓ --- let students = [ { id: '1', name: 'Marc García', role: 'agressor', profiles: ['tdah'], x: 150, y: 150 }, { id: '2', name: 'Laia Soler', role: 'victima', profiles: ['tea'], x: 400, y: 250 }, { id: '3', name: 'Pau Martí', role: 'defensor', profiles: [], x: 450, y: 120 }, { id: '4', name: 'Sílvia Vila', role: 'espectador', profiles: ['immigra'], x: 200, y: 350 } ]; let relations = [ { from: '1', to: '2', type: 'conflicte' }, { from: '3', to: '2', type: 'amistat' } ]; const ROLES = { agressor: { label: 'Agressor/a', color: 'bg-red-500', border: 'border-red-600', text: 'text-red-700' }, victima: { label: 'Víctima', color: 'bg-purple-500', border: 'border-purple-600', text: 'text-purple-700' }, defensor: { label: 'Defensor/a', color: 'bg-green-500', border: 'border-green-600', text: 'text-green-700' }, seguidor: { label: 'Seguidor/a', color: 'bg-orange-500', border: 'border-orange-600', text: 'text-orange-700' }, espectador: { label: 'Espectador/a', color: 'bg-blue-500', border: 'border-blue-600', text: 'text-blue-700' }, none: { label: 'Sense rol', color: 'bg-gray-400', border: 'border-gray-500', text: 'text-gray-600' } }; const INTERVENTIONS = { agressor: ["Establir límits clars", "Treballar l'empatia", "Mediació dirigida"], victima: ["Pla de protecció", "Acompanyament emocional", "Grup de suport entre iguals"], defensor: ["Reforçar la conducta positiva", "Formació en mediació", "Lideratge a l'aula"], tea: ["Anticipació de canvis", "Suport visual", "Guia de relació social"], tdah: ["Adaptació de tasques", "Control d'impulsos", "Espais de pausa activa"], immigra: ["Acompanyament lingüístic", "Pla d'acollida", "Inclusió cultural"] }; let selectedStudentId = null; let draggingNodeId = null; let isConnecting = false; let connectSourceId = null; let connectType = null; let editModeId = null; // --- FUNCIONS PRINCIPALS --- function init() { lucide.createIcons(); renderList(); renderCanvas(); setupDragAndDrop(); } function renderList() { const query = document.getElementById('search').value.toLowerCase(); const container = document.getElementById('student-list'); const filtered = students.filter(s => s.name.toLowerCase().includes(query)); container.innerHTML = filtered.map(s => { const r = ROLES[s.role] || ROLES.none; const isSource = connectSourceId === s.id; return `
${s.name}
${r.label}
${!isConnecting ? ` ` : ''}
`; }).join(''); lucide.createIcons(); } function renderCanvas() { const nodeContainer = document.getElementById('nodes-layer'); const linksContainer = document.getElementById('links-group'); nodeContainer.innerHTML = ''; linksContainer.innerHTML = ''; // Dibuixar Relacions relations.forEach(rel => { const from = students.find(s => s.id === rel.from); const to = students.find(s => s.id === rel.to); if (!from || !to) return; const color = rel.type === 'conflicte' ? '#ef4444' : (rel.type === 'amistat' ? '#22c55e' : '#94a3b8'); const marker = rel.type === 'conflicte' ? 'url(#arrowhead-conflict)' : (rel.type === 'amistat' ? 'url(#arrowhead-friend)' : 'url(#arrowhead)'); const dash = rel.type === 'influencia' ? '5,5' : ''; const line = document.createElementNS("http://www.w3.org/2000/svg", "line"); line.setAttribute("x1", from.x + 24); line.setAttribute("y1", from.y + 24); line.setAttribute("x2", to.x + 24); line.setAttribute("y2", to.y + 24); line.setAttribute("stroke", color); line.setAttribute("stroke-width", "2"); line.setAttribute("stroke-dasharray", dash); line.setAttribute("marker-end", marker); line.setAttribute("class", "cursor-pointer hover:stroke-indigo-400 transition-colors"); line.onclick = (e) => { e.stopPropagation(); removeRelation(rel); }; linksContainer.appendChild(line); }); // Dibuixar Nodes students.forEach(s => { const r = ROLES[s.role] || ROLES.none; const isSource = connectSourceId === s.id; const div = document.createElement('div'); div.className = `student-node absolute pointer-events-auto cursor-grab active:cursor-grabbing flex flex-col items-center group`; div.style.left = `${s.x}px`; div.style.top = `${s.y}px`; div.id = `node-${s.id}`; div.innerHTML = `
${s.name.charAt(0)}
${s.name}
`; nodeContainer.appendChild(div); }); } // --- LOGICA D'INTERACCIÓ --- function handleStudentClick(id) { if (isConnecting) { completeConnection(id); } else { selectStudent(id); } } function selectStudent(id) { selectedStudentId = id; const s = students.find(x => x.id === id); const panel = document.getElementById('details-panel'); const r = ROLES[s.role] || ROLES.none; panel.classList.remove('hidden'); panel.classList.add('flex'); let relevant = [...(INTERVENTIONS[s.role] || [])]; s.profiles.forEach(p => relevant = [...relevant, ...(INTERVENTIONS[p] || [])]); panel.innerHTML = `

${s.name}

${r.label}
${s.profiles.length > 0 ? `
${s.profiles.map(p => `${p}`).join('')}
` : ''}
${relevant.length > 0 ? relevant.map(int => `
${int}
`).join('') : '

No s\'ha determinat un pla d\'acció específic encara.

'}
`; renderList(); renderCanvas(); lucide.createIcons(); } // --- DRAG AND DROP --- function startDragging(e, id) { if (isConnecting) return; // Desactivar drag mentre connectem draggingNodeId = id; } function setupDragAndDrop() { const container = document.getElementById('canvas-container'); container.onmousemove = (e) => { if (draggingNodeId) { const rect = container.getBoundingClientRect(); const x = e.clientX - rect.left - 24; const y = e.clientY - rect.top - 24; const s = students.find(st => st.id === draggingNodeId); s.x = x; s.y = y; renderCanvas(); } }; window.onmouseup = () => { draggingNodeId = null; }; } // --- GESTIÓ DE CONNEXIONS (FLUX MILLORAT) --- function startConnectionFlow(id) { connectSourceId = id; document.getElementById('rel-modal-overlay').classList.remove('hidden'); document.getElementById('rel-modal-overlay').classList.add('flex'); } function initiateConnection(type) { connectType = type; isConnecting = true; closeRelModal(); // Mostrar feedback visual document.getElementById('connection-banner').classList.remove('hidden'); document.getElementById('connection-banner').classList.add('flex'); document.getElementById('app').classList.add('connecting-mode'); renderList(); renderCanvas(); } function completeConnection(targetId) { if (targetId === connectSourceId) { cancelConnection(); return; } // Evitar duplicats const exists = relations.some(r => r.from === connectSourceId && r.to === targetId && r.type === connectType); if (!exists) { relations.push({ from: connectSourceId, to: targetId, type: connectType }); } cancelConnection(); } function cancelConnection() { isConnecting = false; connectSourceId = null; connectType = null; document.getElementById('connection-banner').classList.add('hidden'); document.getElementById('connection-banner').classList.remove('flex'); document.getElementById('app').classList.remove('connecting-mode'); renderList(); renderCanvas(); } // --- ALTRES MODALS --- function openAddModal() { editModeId = null; document.getElementById('modal-title').innerText = "Nou Alumne"; document.getElementById('form-name').value = ""; document.getElementById('form-role').value = "none"; document.querySelectorAll('input[name="profile"]').forEach(c => c.checked = false); document.getElementById('modal-overlay').classList.remove('hidden'); document.getElementById('modal-overlay').classList.add('flex'); } function openEditModal(id) { editModeId = id; const s = students.find(x => x.id === id); document.getElementById('modal-title').innerText = "Editar Alumne"; document.getElementById('form-name').value = s.name; document.getElementById('form-role').value = s.role; document.querySelectorAll('input[name="profile"]').forEach(c => { c.checked = s.profiles.includes(c.value); }); document.getElementById('modal-overlay').classList.remove('hidden'); document.getElementById('modal-overlay').classList.add('flex'); } function closeModal() { document.getElementById('modal-overlay').classList.add('hidden'); document.getElementById('modal-overlay').classList.remove('flex'); } function closeRelModal() { document.getElementById('rel-modal-overlay').classList.add('hidden'); document.getElementById('rel-modal-overlay').classList.remove('flex'); } function saveStudent() { const name = document.getElementById('form-name').value; const role = document.getElementById('form-role').value; const profiles = Array.from(document.querySelectorAll('input[name="profile"]:checked')).map(c => c.value); if (!name) return; if (editModeId) { const s = students.find(x => x.id === editModeId); s.name = name; s.role = role; s.profiles = profiles; } else { students.push({ id: Date.now().toString(), name, role, profiles, x: Math.random() * 300 + 50, y: Math.random() * 300 + 50 }); } closeModal(); renderList(); renderCanvas(); if(selectedStudentId) selectStudent(selectedStudentId); } function deleteStudent(id) { students = students.filter(s => s.id !== id); relations = relations.filter(r => r.from !== id && r.to !== id); closeDetails(); renderList(); renderCanvas(); } function removeRelation(rel) { relations = relations.filter(r => !(r.from === rel.from && r.to === rel.to && r.type === rel.type)); renderCanvas(); } function closeDetails() { selectedStudentId = null; document.getElementById('details-panel').classList.add('hidden'); document.getElementById('details-panel').classList.remove('flex'); renderList(); renderCanvas(); } function exportData() { const data = { students, relations }; const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `sociograma_${new Date().toLocaleDateString()}.json`; a.click(); } window.onload = init;