{"id":6107,"date":"2026-03-13T14:35:30","date_gmt":"2026-03-13T17:35:30","guid":{"rendered":"https:\/\/fisica2.fica.unsl.edu.ar\/?page_id=6107"},"modified":"2026-04-18T18:25:42","modified_gmt":"2026-04-18T21:25:42","slug":"graficar_sup_equipotencial_lin_campo","status":"publish","type":"page","link":"https:\/\/fisica2.fica.unsl.edu.ar\/index.php\/graficar_sup_equipotencial_lin_campo\/","title":{"rendered":"graficar_sup_equipotencial_lin_campo"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"6107\" class=\"elementor elementor-6107\">\n\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-8d78edf elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"8d78edf\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-99f902d\" data-id=\"99f902d\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-d45815a elementor-widget elementor-widget-html\" data-id=\"d45815a\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!DOCTYPE html>\r\n<html lang=\"es\">\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <title>Laboratorio Virtual - Cuba Electrolitica<\/title>\r\n    <script src=\"https:\/\/cdn.tailwindcss.com\"><\/script>\r\n    <link href=\"https:\/\/fonts.googleapis.com\/css2?family=JetBrains+Mono:wght@400;600;700&family=Space+Grotesk:wght@300;400;500;600;700&display=swap\" rel=\"stylesheet\">\r\n    <style>\r\n        :root {\r\n            --bg-primary: #0a0e17;\r\n            --bg-secondary: #111827;\r\n            --bg-tertiary: #1a2332;\r\n            --fg-primary: #e2e8f0;\r\n            --fg-secondary: #94a3b8;\r\n            --fg-muted: #64748b;\r\n            --accent-cyan: #22d3ee;\r\n            --positive: #ef4444;\r\n            --negative: #3b82f6;\r\n            --border: #2d3748;\r\n        }\r\n\r\n        * { box-sizing: border-box; }\r\n        body {\r\n            font-family: 'Space Grotesk', sans-serif;\r\n            background: var(--bg-primary);\r\n            color: var(--fg-primary);\r\n            margin: 0;\r\n            min-height: 100vh;\r\n            overflow-x: hidden;\r\n        }\r\n        .mono { font-family: 'JetBrains Mono', monospace; }\r\n        .panel {\r\n            background: var(--bg-secondary);\r\n            border: 1px solid var(--border);\r\n            border-radius: 12px;\r\n        }\r\n        .instrument-display {\r\n            background: linear-gradient(145deg, #0d1117 0%, #161b22 100%);\r\n            border: 2px solid #30363d;\r\n            border-radius: 8px;\r\n            box-shadow: inset 0 2px 10px rgba(0,0,0,0.5);\r\n        }\r\n        .lcd-display {\r\n            background: #0f1a0f;\r\n            color: #22c55e;\r\n            font-family: 'JetBrains Mono', monospace;\r\n            text-shadow: 0 0 10px rgba(34, 197, 94, 0.6);\r\n            border-radius: 4px;\r\n            padding: 12px 16px;\r\n        }\r\n        .btn-control {\r\n            background: linear-gradient(145deg, #2d3748, #1a202c);\r\n            border: 1px solid #4a5568;\r\n            color: var(--fg-primary);\r\n            padding: 8px 16px;\r\n            border-radius: 6px;\r\n            cursor: pointer;\r\n            transition: all 0.15s ease;\r\n            font-weight: 500;\r\n        }\r\n        .btn-control:hover { background: linear-gradient(145deg, #3d4758, #2a303c); }\r\n        .btn-control.active { background: linear-gradient(145deg, #2563eb, #1d4ed8); border-color: #3b82f6; }\r\n        \r\n        input[type=\"range\"] {\r\n            -webkit-appearance: none;\r\n            width: 100%;\r\n            height: 8px;\r\n            background: linear-gradient(90deg, var(--negative), #6b7280, var(--positive));\r\n            border-radius: 4px;\r\n        }\r\n        input[type=\"range\"]::-webkit-slider-thumb {\r\n            -webkit-appearance: none;\r\n            width: 20px; height: 20px;\r\n            background: var(--fg-primary);\r\n            border-radius: 50%;\r\n            cursor: pointer;\r\n        }\r\n        \r\n        .checkbox-custom {\r\n            appearance: none; width: 18px; height: 18px;\r\n            border: 2px solid var(--border); border-radius: 4px;\r\n            background: var(--bg-tertiary); cursor: pointer; position: relative;\r\n        }\r\n        .checkbox-custom:checked { background: var(--accent-cyan); border-color: var(--accent-cyan); }\r\n        .checkbox-custom:checked::after { content: '\u2713'; position: absolute; color: var(--bg-primary); font-size: 12px; top: 50%; left: 50%; transform: translate(-50%, -50%); }\r\n        \r\n        .led-indicator { width: 12px; height: 12px; border-radius: 50%; background: #374151; box-shadow: inset 0 1px 3px rgba(0,0,0,0.5); }\r\n        .led-indicator.on { background: #22c55e; box-shadow: 0 0 10px rgba(34, 197, 94, 0.6); }\r\n        \r\n        #mainCanvas { cursor: crosshair; }\r\n        .data-table { max-height: 200px; overflow-y: auto; }\r\n        .section-title { font-size: 11px; text-transform: uppercase; letter-spacing: 1.5px; color: var(--fg-muted); margin-bottom: 12px; border-bottom: 1px solid var(--border); padding-bottom: 8px; }\r\n        \r\n        .visualization-label { color: #ffffff; }\r\n        \r\n        .data-table tbody td {\r\n            background-color: #f8fafc;\r\n            color: #111827;\r\n            border: 1px solid #e2e8f0;\r\n        }\r\n        .data-table thead th {\r\n            background-color: var(--bg-tertiary);\r\n            color: var(--fg-muted);\r\n            border: 1px solid var(--border);\r\n            position: sticky;\r\n            top: 0;\r\n            z-index: 10;\r\n        }\r\n        \r\n        .used-point-row { opacity: 0.6; background-color: #e5e7eb !important; }\r\n        \r\n        \/* Estilos para la lista de curvas generadas *\/\r\n        .generated-list { max-height: 150px; overflow-y: auto; }\r\n        .generated-item {\r\n            display: flex;\r\n            justify-content: space-between;\r\n            align-items: center;\r\n            padding: 4px 8px;\r\n            border-bottom: 1px solid var(--border);\r\n            font-size: 12px;\r\n        }\r\n        .btn-delete-small {\r\n            background: #ef4444;\r\n            color: white;\r\n            border: none;\r\n            border-radius: 4px;\r\n            padding: 2px 6px;\r\n            font-size: 10px;\r\n            cursor: pointer;\r\n        }\r\n        .btn-delete-small:hover { background: #dc2626; }\r\n        \r\n        \r\n        \r\n        \r\n    <\/style>\r\n<\/head>\r\n<body class=\"p-4\">\r\n    <div class=\"max-w-[1800px] mx-auto\">\r\n       \r\n       \r\n       <header class=\"mb-4 flex items-center justify-between flex-wrap gap-2\">\r\n    <div>\r\n        <div class=\"flex items-center gap-8\"> \r\n            <h1 class=\"text-2xl font-bold tracking-tight\">Laboratorio Virtual - Cuba Electrolitica<\/h1>\r\n            \r\n            <div class=\"flex items-center gap-2\">\r\n                <a href=\"https:\/\/fisica2.fica.unsl.edu.ar\/index.php\/FISAR_SIM\/\" target=\"_blank\">\r\n                <img decoding=\"async\" src=\"FISAR_SIM_1\" alt=\"Logo Fluxar\" class=\"max-h-8 w-auto object-contain\"><\/a>\r\n                <span style=\"font-size: 0.75rem; font-weight: 600; line-height: 1; color: #94a3b8; white-space: nowrap;\">\r\n                    FICA - UNSL\r\n                <\/span>\r\n            <\/div>\r\n        <\/div>\r\n        \r\n        <p class=\"text-sm text-[var(--fg-secondary)] mt-1\">Simulacion interactiva de superficies equipotenciales y lineas de campo<\/p>\r\n    <\/div>\r\n\r\n    <div class=\"flex items-center gap-10\">\r\n        <div class=\"flex items-center gap-2 text-sm\">\r\n            <div class=\"led-indicator off\" id=\"statusLed\"><\/div>\r\n            <span class=\"text-[var(--fg-secondary)]\" id=\"statusText\">Sistema inactivo<\/span>\r\n        <\/div>\r\n        <button class=\"btn-control text-sm\" onclick=\"resetSimulation()\">Reiniciar Todo<\/button>\r\n    <\/div>\r\n<\/header>\r\n       \r\n       \r\n       \r\n       \r\n\r\n        <div class=\"grid grid-cols-[320px_1fr_300px] gap-4\">\r\n            <!-- Panel Izquierdo -->\r\n            <div class=\"panel p-4 space-y-4 overflow-y-auto max-h-[calc(100vh-120px)]\">\r\n                <section>\r\n                    <h2 class=\"section-title\">Fuente de Alimentacion<\/h2>\r\n                    <div class=\"instrument-display p-4\">\r\n                        <div class=\"flex items-center justify-between mb-4\">\r\n                            <span class=\"text-sm font-medium\">Tension<\/span>\r\n                            <div class=\"flex items-center gap-2\">\r\n                                <div class=\"led-indicator\" id=\"powerLed\"><\/div>\r\n                                <span class=\"text-xs text-[var(--fg-muted)]\" id=\"powerStatus\">OFF<\/span>\r\n                            <\/div>\r\n                        <\/div>\r\n                        <div class=\"lcd-display text-center text-3xl font-bold mb-4\" id=\"voltageDisplay\">0.00 V<\/div>\r\n                        <div class=\"mb-4\">\r\n                            <input type=\"range\" id=\"voltageSlider\" min=\"0\" max=\"20\" step=\"0.1\" value=\"0\">\r\n                        <\/div>\r\n                        <div class=\"flex gap-2\">\r\n                            <button class=\"btn-control flex-1\" id=\"powerBtn\" onclick=\"togglePower()\">Encender<\/button>\r\n                            <button class=\"btn-control\" onclick=\"invertPolarity()\">Invertir<\/button>\r\n                        <\/div>\r\n                    <\/div>\r\n                <\/section>\r\n\r\n                <section>\r\n                    <h2 class=\"section-title\">Configuracion<\/h2>\r\n                    <select id=\"configSelect\" class=\"w-full mb-3 bg-gray-100 border border-gray-300 p-2 rounded text-gray-900 font-medium\" onchange=\"loadConfiguration()\">\r\n                        <option value=\"dipole\">Dipolo (dos circulos)<\/option>\r\n                        <option value=\"plates\">Placas paralelas<\/option>\r\n                        <option value=\"pointline\">Carga y linea<\/option>\r\n                    <\/select>\r\n                <\/section>\r\n\r\n                <section>\r\n                    <h2 class=\"section-title\">Visualizacion<\/h2>\r\n                    <div class=\"space-y-2\">\r\n                        <label class=\"flex items-center gap-3 cursor-pointer\">\r\n                            <input type=\"checkbox\" class=\"checkbox-custom\" id=\"showGrid\" checked onchange=\"render()\">\r\n                            <span class=\"text-sm visualization-label\">Cuadricula<\/span>\r\n                        <\/label>\r\n                        <label class=\"flex items-center gap-3 cursor-pointer\">\r\n                            <input type=\"checkbox\" class=\"checkbox-custom\" id=\"showUserCurves\" checked onchange=\"render()\">\r\n                            <span class=\"text-sm visualization-label\">Curvas de Medicion<\/span>\r\n                        <\/label>\r\n                        <label class=\"flex items-center gap-3 cursor-pointer\">\r\n                            <input type=\"checkbox\" class=\"checkbox-custom\" id=\"showFieldLines\" checked onchange=\"render()\">\r\n                            <span class=\"text-sm visualization-label\">Lineas de Campo (Trazadas)<\/span>\r\n                        <\/label>\r\n                        <label class=\"flex items-center gap-3 cursor-pointer\">\r\n                            <input type=\"checkbox\" class=\"checkbox-custom\" id=\"showPoints\" checked onchange=\"render()\">\r\n                            <span class=\"text-sm visualization-label\">Puntos Medidos<\/span>\r\n                        <\/label>\r\n                    <\/div>\r\n                <\/section>\r\n\r\n                <section>\r\n                    <h2 class=\"section-title\">Herramientas<\/h2>\r\n                     <div class=\"text-xs text-[var(--fg-muted)] p-2 bg-[var(--bg-tertiary)] rounded border border-[var(--border)] mb-3\">\r\n                        <strong>Instrucciones:<\/strong><br>\r\n                        1. Mida puntos haciendo clic (min 5).<br>\r\n                        2. Fije la traza correspondiente.<br>\r\n                        3. Exporte datos o imagen.\r\n                    <\/div>\r\n                    \r\n                    <button class=\"btn-control w-full bg-[var(--accent-cyan)] text-black font-bold mb-2\" onclick=\"generateCurvesFromPoints()\">\r\n                        Fijar Equipotencial\r\n                    <\/button>\r\n\r\n                    <button class=\"btn-control w-full bg-orange-500 text-black font-bold\" onclick=\"generateFieldLinesFromPoints()\">\r\n                        Fijar Linea de Campo\r\n                    <\/button>\r\n                <\/section>\r\n            <\/div>\r\n\r\n            <!-- Panel Central -->\r\n            <div class=\"panel overflow-hidden relative\" style=\"height: calc(100vh - 140px);\">\r\n                <div class=\"absolute top-0 left-0 right-0 bg-[var(--bg-tertiary)] px-4 py-2 flex items-center justify-between border-b border-[var(--border)] z-10\">\r\n                    <span class=\"text-sm font-medium\">Cuba Electrolitica<\/span>\r\n                    <div class=\"flex items-center gap-4 text-xs mono\">\r\n                        <span>Sonda: (<span id=\"probeX\">---<\/span>, <span id=\"probeY\">---<\/span>) cm<\/span>\r\n                        <span>Zoom: <span id=\"zoomLevel\">100<\/span>%<\/span>\r\n                    <\/div>\r\n                <\/div>\r\n                \r\n                <canvas id=\"mainCanvas\" class=\"w-full h-full\"><\/canvas>\r\n                \r\n                <div class=\"absolute bottom-4 right-4 flex gap-2 z-10\">\r\n                    <button class=\"btn-control px-3 py-2\" onclick=\"zoomIn()\">+<\/button>\r\n                    <button class=\"btn-control px-3 py-2\" onclick=\"zoomOut()\">-<\/button>\r\n                    <button class=\"btn-control px-3 py-2\" onclick=\"resetView()\">\u27f2<\/button>\r\n                <\/div>\r\n            <\/div>\r\n\r\n            <!-- Panel Derecho -->\r\n            <div class=\"space-y-4\">\r\n                <div class=\"panel p-4\">\r\n                    <h2 class=\"section-title\">Voltimetro<\/h2>\r\n                    <div class=\"instrument-display p-4\">\r\n                        <div class=\"text-center mb-2\"><span class=\"text-xs text-[var(--fg-muted)]\">V(x, y)<\/span><\/div>\r\n                        <div class=\"lcd-display text-center text-4xl font-bold\" id=\"voltmeterDisplay\">0.00 V<\/div>\r\n                    <\/div>\r\n                <\/div>\r\n\r\n                <div class=\"panel p-4\">\r\n                    <h2 class=\"section-title\">Registro de Datos<\/h2>\r\n                    <div class=\"data-table rounded overflow-hidden\">\r\n                        <table class=\"w-full text-xs mono\">\r\n                            <thead>\r\n                                <tr>\r\n                                    <th class=\"text-left py-1 px-2\">#<\/th>\r\n                                    <th class=\"text-right py-1 px-2\">X<\/th>\r\n                                    <th class=\"text-right py-1 px-2\">Y<\/th>\r\n                                    <th class=\"text-right py-1 px-2\">V<\/th>\r\n                                <\/tr>\r\n                            <\/thead>\r\n                            <tbody id=\"pointsTable\"><\/tbody>\r\n                        <\/table>\r\n                    <\/div>\r\n                    <div class=\"flex flex-col gap-2 mt-3\">\r\n                        <button class=\"btn-control flex-1 text-xs\" onclick=\"exportCSV()\">Exportar Todo a CSV<\/button>\r\n                        <button class=\"btn-control flex-1 text-xs bg-green-700 text-white\" onclick=\"downloadImage()\">Guardar Imagen<\/button>\r\n                    <\/div>\r\n                <\/div>\r\n                \r\n                <div class=\"panel p-4\">\r\n                    <h2 class=\"section-title\">Informacion<\/h2>\r\n                    <div class=\"text-sm space-y-1\">\r\n                        <div class=\"flex justify-between\">\r\n                            <span class=\"text-[var(--fg-secondary)]\">Puntos activos:<\/span>\r\n                            <span class=\"mono\" id=\"totalPoints\">0<\/span>\r\n                        <\/div>\r\n                        <div class=\"flex justify-between\">\r\n                            <span class=\"text-[var(--fg-secondary)]\">Equipotenciales fijadas:<\/span>\r\n                            <span class=\"mono\" id=\"totalCurves\">0<\/span>\r\n                        <\/div>\r\n                         <div class=\"flex justify-between\">\r\n                            <span class=\"text-[var(--fg-secondary)]\">Lineas de campo fijadas:<\/span>\r\n                            <span class=\"mono\" id=\"totalFieldLines\">0<\/span>\r\n                        <\/div>\r\n                    <\/div>\r\n                <\/div>\r\n\r\n                <!-- NUEVA SECCI\u00d3N: CURVAS GENERADAS -->\r\n                <div class=\"panel p-4\" id=\"generatedSection\">\r\n                    <h2 class=\"section-title\">Trazas Generadas<\/h2>\r\n                    <div class=\"generated-list space-y-1\" id=\"generatedList\">\r\n                        <!-- Se llena din\u00e1micamente -->\r\n                        <div class=\"text-xs text-center text-[var(--fg-muted)]\">Ninguna traza generada.<\/div>\r\n                    <\/div>\r\n                <\/div>\r\n            <\/div>\r\n        <\/div>\r\n    <\/div>\r\n\r\n    <script>\r\n        \/\/ ==================== CORE SETUP ====================\r\n        const GRID_SIZE = 200; \r\n        const EPSILON = 1e-10;\r\n        const MIN_POINTS_CURVE = 5; \/\/ M\u00ednimo de puntos requeridos\r\n        \r\n        let canvas, ctx;\r\n        let state = {\r\n            powerOn: false, voltage: 0, polarity: 1, electrodes: [],\r\n            measuredPoints: [],\r\n            allPoints: [],\r\n            userCurves: [],\r\n            userFieldLines: [],\r\n            draggingElectrode: null,\r\n            probe: { x: 0, y: 0 },\r\n            view: { zoom: 1, offsetX: 0, offsetY: 0 },\r\n            gridCache: null, gridBounds: null, needsRecalculation: true\r\n        };\r\n\r\n        const configurations = {\r\n            dipole: () => [\r\n                { type: 'circle', x: -3, y: 0, radius: 0.5, charge: 1, color: '#ef4444', label: '+' },\r\n                { type: 'circle', x: 3, y: 0, radius: 0.5, charge: -1, color: '#3b82f6', label: '-' }\r\n            ],\r\n            plates: () => [\r\n                { type: 'plate', x: -4, y: 0, width: 0.3, height: 6, charge: 1, color: '#ef4444', label: '+' },\r\n                { type: 'plate', x: 4, y: 0, width: 0.3, height: 6, charge: -1, color: '#3b82f6', label: '-' }\r\n            ],\r\n            pointline: () => [\r\n                { type: 'circle', x: -3, y: 0, radius: 0.4, charge: 1, color: '#ef4444', label: '+' },\r\n                { type: 'plate', x: 3, y: 0, width: 0.3, height: 6, charge: -1, color: '#3b82f6', label: '-' }\r\n            ]\r\n        };\r\n\r\n        \/\/ ==================== INITIALIZATION ====================\r\n        window.addEventListener('DOMContentLoaded', () => {\r\n            canvas = document.getElementById('mainCanvas');\r\n            ctx = canvas.getContext('2d');\r\n            resizeCanvas();\r\n            window.addEventListener('resize', resizeCanvas);\r\n            \r\n            canvas.addEventListener('mousemove', handleMouseMove);\r\n            canvas.addEventListener('mousedown', handleMouseDown);\r\n            canvas.addEventListener('contextmenu', handleRightClick);\r\n            canvas.addEventListener('wheel', handleWheel, { passive: false });\r\n            canvas.addEventListener('mouseup', () => { state.draggingElectrode = null; canvas.style.cursor = 'crosshair'; });\r\n            \r\n            state.voltage = parseFloat(document.getElementById('voltageSlider').value);\r\n            loadConfiguration();\r\n            requestAnimationFrame(loop);\r\n        });\r\n\r\n        function resizeCanvas() {\r\n            const rect = canvas.parentElement.getBoundingClientRect();\r\n            canvas.width = rect.width;\r\n            canvas.height = rect.height;\r\n            state.needsRecalculation = true;\r\n        }\r\n\r\n        \/\/ ==================== PHYSICS ENGINE ====================\r\n        function calculatePotential(x, y) {\r\n            if (!state.powerOn || state.electrodes.length === 0) return 0;\r\n            \r\n            const V_max = state.voltage; \r\n            \r\n            let posElectrodes = state.electrodes.filter(e => e.charge > 0);\r\n            let negElectrodes = state.electrodes.filter(e => e.charge < 0);\r\n            \r\n            function isInside(electrode) {\r\n                if (electrode.type === 'circle') {\r\n                    let dist = Math.sqrt((x - electrode.x)**2 + (y - electrode.y)**2);\r\n                    return dist <= electrode.radius;\r\n                } else if (electrode.type === 'plate') {\r\n                    let w = electrode.width \/ 2;\r\n                    let h = electrode.height \/ 2;\r\n                    return x >= electrode.x - w && x <= electrode.x + w &&\r\n                           y >= electrode.y - h && y <= electrode.y + h;\r\n                }\r\n                return false;\r\n            }\r\n            \r\n            for (let e of posElectrodes) {\r\n                if (isInside(e)) return state.polarity > 0 ? V_max : 0;\r\n            }\r\n            for (let e of negElectrodes) {\r\n                if (isInside(e)) return state.polarity > 0 ? 0 : V_max;\r\n            }\r\n            \r\n            let plates = state.electrodes.filter(e => e.type === 'plate');\r\n            if (plates.length === 2) {\r\n                let p1 = plates[0];\r\n                let p2 = plates[1];\r\n                let leftP = p1.x < p2.x ? p1 : p2;\r\n                let rightP = p1.x < p2.x ? p2 : p1;\r\n                let xLeft = leftP.x + leftP.width\/2;\r\n                let xRight = rightP.x - rightP.width\/2;\r\n                let dist = Math.abs(xRight - xLeft);\r\n                \r\n                if (x > xLeft && x < xRight && dist > EPSILON) {\r\n                    let V_val = 0;\r\n                    if (leftP.charge > 0) {\r\n                        V_val = V_max * (1 - (x - xLeft) \/ dist);\r\n                    } else {\r\n                        V_val = V_max * (x - xLeft) \/ dist;\r\n                    }\r\n                    return state.polarity > 0 ? V_val : V_max - V_val;\r\n                }\r\n            }\r\n            \r\n            let minDistPos = Infinity;\r\n            for (let e of posElectrodes) {\r\n                let dist = Math.sqrt((x - e.x)**2 + (y - e.y)**2);\r\n                if (e.type === 'plate') {\r\n                    let dx = Math.max(0, Math.abs(x - e.x) - e.width\/2);\r\n                    let dy = Math.max(0, Math.abs(y - e.y) - e.height\/2);\r\n                    dist = Math.sqrt(dx*dx + dy*dy);\r\n                }\r\n                if (dist < minDistPos) minDistPos = dist;\r\n            }\r\n            \r\n            let minDistNeg = Infinity;\r\n            for (let e of negElectrodes) {\r\n                let dist = Math.sqrt((x - e.x)**2 + (y - e.y)**2);\r\n                if (e.type === 'plate') {\r\n                    let dx = Math.max(0, Math.abs(x - e.x) - e.width\/2);\r\n                    let dy = Math.max(0, Math.abs(y - e.y) - e.height\/2);\r\n                    dist = Math.sqrt(dx*dx + dy*dy);\r\n                }\r\n                if (dist < minDistNeg) minDistNeg = dist;\r\n            }\r\n            \r\n            minDistPos = Math.max(0.01, minDistPos);\r\n            minDistNeg = Math.max(0.01, minDistNeg);\r\n            \r\n            let wPos = 1.0 \/ minDistPos;\r\n            let wNeg = 1.0 \/ minDistNeg;\r\n            \r\n            let V_norm = wPos \/ (wPos + wNeg);\r\n            \r\n            let V_final = V_norm * V_max;\r\n            \r\n            if (state.polarity < 0) {\r\n                V_final = V_max - V_final;\r\n            }\r\n            \r\n            return V_final;\r\n        }\r\n\r\n        function updateGrid() {\r\n            if (!state.needsRecalculation) return;\r\n            const bounds = getViewBounds();\r\n            state.gridBounds = bounds;\r\n            state.gridCache = new Float32Array(GRID_SIZE * GRID_SIZE);\r\n            for (let i = 0; i < GRID_SIZE; i++) {\r\n                for (let j = 0; j < GRID_SIZE; j++) {\r\n                    const x = bounds.minX + (bounds.maxX - bounds.minX) * (i \/ GRID_SIZE);\r\n                    const y = bounds.minY + (bounds.maxY - bounds.minY) * (j \/ GRID_SIZE);\r\n                    state.gridCache[i + j * GRID_SIZE] = calculatePotential(x, y);\r\n                }\r\n            }\r\n            state.needsRecalculation = false;\r\n        }\r\n\r\n        function getViewBounds() {\r\n            const scale = 10 \/ state.view.zoom;\r\n            const aspect = canvas.height \/ canvas.width;\r\n            return {\r\n                minX: -scale + state.view.offsetX,\r\n                maxX: scale + state.view.offsetX,\r\n                minY: -scale * aspect + state.view.offsetY,\r\n                maxY: scale * aspect + state.view.offsetY\r\n            };\r\n        }\r\n\r\n        function worldToScreen(wx, wy) {\r\n            const b = state.gridBounds || getViewBounds();\r\n            const sx = (wx - b.minX) \/ (b.maxX - b.minX) * canvas.width;\r\n            const sy = canvas.height - (wy - b.minY) \/ (b.maxY - b.minY) * canvas.height;\r\n            return { x: sx, y: sy };\r\n        }\r\n\r\n        function screenToWorld(sx, sy) {\r\n            const b = getViewBounds();\r\n            const wx = b.minX + (sx \/ canvas.width) * (b.maxX - b.minX);\r\n            const wy = b.minY + ((canvas.height - sy) \/ canvas.height) * (b.maxY - b.minY);\r\n            return { x: wx, y: wy };\r\n        }\r\n\r\n        \/\/ ==================== RENDERING ====================\r\n        function loop() {\r\n            if (state.powerOn && state.needsRecalculation) updateGrid();\r\n            render();\r\n            requestAnimationFrame(loop);\r\n        }\r\n\r\n        function render() {\r\n            ctx.fillStyle = '#0a0e17';\r\n            ctx.fillRect(0, 0, canvas.width, canvas.height);\r\n            if (document.getElementById('showGrid').checked) drawGrid();\r\n            \r\n            \/\/ Dibujar curvas y puntos\r\n            if (document.getElementById('showUserCurves').checked) drawUserCurves();\r\n            if (document.getElementById('showFieldLines').checked) drawUserFieldLines();\r\n            drawElectrodes();\r\n            if (document.getElementById('showPoints').checked) drawMeasuredPoints();\r\n            drawProbe();\r\n        }\r\n\r\n        function drawGrid() {\r\n            const b = getViewBounds();\r\n            ctx.strokeStyle = 'rgba(45, 55, 72, 0.4)';\r\n            ctx.lineWidth = 1;\r\n            const step = 1;\r\n            ctx.beginPath();\r\n            for (let x = Math.floor(b.minX); x <= Math.ceil(b.maxX); x += step) {\r\n                let p1 = worldToScreen(x, b.minY); let p2 = worldToScreen(x, b.maxY);\r\n                ctx.moveTo(p1.x, p1.y); ctx.lineTo(p2.x, p2.y);\r\n            }\r\n            for (let y = Math.floor(b.minY); y <= Math.ceil(b.maxY); y += step) {\r\n                let p1 = worldToScreen(b.minX, y); let p2 = worldToScreen(b.maxX, y);\r\n                ctx.moveTo(p1.x, p1.y); ctx.lineTo(p2.x, p2.y);\r\n            }\r\n            ctx.stroke();\r\n        }\r\n\r\n        function drawElectrodes() {\r\n            ctx.lineWidth = 2;\r\n            for (let el of state.electrodes) {\r\n                let p = worldToScreen(el.x, el.y);\r\n                ctx.fillStyle = el.color;\r\n                ctx.strokeStyle = 'white';\r\n                if (el.type === 'circle') {\r\n                    let r = el.radius \/ (getViewBounds().maxX - getViewBounds().minX) * canvas.width;\r\n                    ctx.beginPath(); ctx.arc(p.x, p.y, Math.max(5, r), 0, Math.PI*2); ctx.fill(); ctx.stroke();\r\n                    ctx.fillStyle = '#000000'; ctx.font = 'bold 14px sans-serif'; ctx.textAlign='center'; ctx.textBaseline='middle';\r\n                    ctx.fillText(el.label, p.x, p.y);\r\n                } else if (el.type === 'plate') {\r\n                    let w = el.width \/ (getViewBounds().maxX - getViewBounds().minX) * canvas.width;\r\n                    let h = el.height \/ (getViewBounds().maxY - getViewBounds().minY) * canvas.height;\r\n                    ctx.fillRect(p.x - w\/2, p.y - h\/2, w, h);\r\n                    ctx.strokeRect(p.x - w\/2, p.y - h\/2, w, h);\r\n                    ctx.fillStyle = '#000000'; ctx.font = 'bold 12px sans-serif'; ctx.textAlign='center';\r\n                    ctx.fillText(el.label, p.x, p.y);\r\n                }\r\n            }\r\n        }\r\n\r\n        function drawMeasuredPoints() {\r\n            for (let pt of state.allPoints) {\r\n                let p = worldToScreen(pt.x, pt.y);\r\n                if (pt.used) {\r\n                    ctx.fillStyle = 'rgba(150, 150, 150, 0.5)';\r\n                    ctx.beginPath(); ctx.arc(p.x, p.y, 4, 0, Math.PI*2); ctx.fill();\r\n                } else {\r\n                    ctx.fillStyle = 'white';\r\n                    ctx.beginPath(); ctx.arc(p.x, p.y, 6, 0, Math.PI*2); ctx.fill();\r\n                    ctx.fillStyle = '#22c55e';\r\n                    ctx.beginPath(); ctx.arc(p.x, p.y, 4, 0, Math.PI*2); ctx.fill();\r\n                }\r\n            }\r\n        }\r\n\r\n        \/\/ ==================== TRAZADO DE CURVAS (Interpolaci\u00f3n Mejorada) ====================\r\n        \r\n        \/\/ Algoritmo de suavizado tipo B-Spline aproximada\r\n        function drawSmoothCurve(points, color, dashed = false) {\r\n            if (points.length < 2) return;\r\n\r\n            ctx.strokeStyle = color;\r\n            ctx.fillStyle = color;\r\n            ctx.lineWidth = 3;\r\n            if (dashed) ctx.setLineDash([8, 4]);\r\n            else ctx.setLineDash([]);\r\n\r\n            ctx.beginPath();\r\n\r\n            \/\/ Convertir a pantalla\r\n            let screenPoints = points.map(p => worldToScreen(p.x, p.y));\r\n            \r\n            ctx.moveTo(screenPoints[0].x, screenPoints[0].y);\r\n\r\n            if (screenPoints.length === 2) {\r\n                ctx.lineTo(screenPoints[1].x, screenPoints[1].y);\r\n            } else {\r\n                \/\/ Algoritmo: Promediado de esquinas (Corner Cutting \/ Chaikin's algorithm simplificado)\r\n                \/\/ Esto crea una curva que NO pasa por los puntos, sino que los \"suaviza\"\r\n                \/\/ similar a una B-Spline.\r\n                \r\n                \/\/ Iteramos 2 veces para mayor suavidad\r\n                let tempPts = [...screenPoints];\r\n                for (let iter = 0; iter < 2; iter++) {\r\n                    let newPts = [];\r\n                    newPts.push(tempPts[0]); \/\/ Mantener inicio\r\n                    for (let i = 0; i < tempPts.length - 1; i++) {\r\n                        let p0 = tempPts[i];\r\n                        let p1 = tempPts[i+1];\r\n                        \/\/ Crear puntos intermedios 1\/4 y 3\/4\r\n                        let q = { x: 0.75 * p0.x + 0.25 * p1.x, y: 0.75 * p0.y + 0.25 * p1.y };\r\n                        let r = { x: 0.25 * p0.x + 0.75 * p1.x, y: 0.25 * p0.y + 0.75 * p1.y };\r\n                        newPts.push(q);\r\n                        newPts.push(r);\r\n                    }\r\n                    newPts.push(tempPts[tempPts.length - 1]); \/\/ Mantener fin\r\n                    tempPts = newPts;\r\n                }\r\n                \r\n                \/\/ Dibujar la curva suavizada como polyline (los puntos est\u00e1n muy juntos, se ve curvo)\r\n                ctx.moveTo(tempPts[0].x, tempPts[0].y);\r\n                for(let i=1; i<tempPts.length; i++) {\r\n                    ctx.lineTo(tempPts[i].x, tempPts[i].y);\r\n                }\r\n            }\r\n            \r\n            ctx.stroke();\r\n            ctx.setLineDash([]);\r\n            ctx.lineWidth = 1;\r\n        }\r\n\r\n        function drawUserCurves() {\r\n            for (let curve of state.userCurves) {\r\n                if (curve.points.length < 2) continue;\r\n                drawSmoothCurve(curve.points, curve.color, true);\r\n                \r\n                \/\/ Etiqueta\r\n                let screenPoints = curve.points.map(p => worldToScreen(p.x, p.y));\r\n                let labelPoint = screenPoints[Math.floor(screenPoints.length \/ 2)];\r\n                ctx.fillStyle = 'rgba(0, 0, 0, 0.8)'; \r\n                ctx.fillRect(labelPoint.x - 30, labelPoint.y - 20, 60, 20);\r\n                ctx.fillStyle = curve.color; \r\n                ctx.font = 'bold 12px JetBrains Mono'; \r\n                ctx.textAlign = 'center';\r\n                ctx.fillText(curve.averagePotential.toFixed(1) + \" V\", labelPoint.x, labelPoint.y - 6);\r\n            }\r\n        }\r\n\r\n        function drawUserFieldLines() {\r\n            for (let line of state.userFieldLines) {\r\n                if (line.points.length < 2) continue;\r\n                \r\n                ctx.strokeStyle = '#fb923c'; \r\n                ctx.fillStyle = '#fb923c';\r\n                ctx.lineWidth = 2;\r\n                ctx.setLineDash([]);\r\n                ctx.beginPath();\r\n                \r\n                let screenPoints = line.points.map(p => worldToScreen(p.x, p.y));\r\n                \r\n                \/\/ Suavizado tipo B-Spline para l\u00edneas de campo\r\n                let tempPts = [...screenPoints];\r\n                if (tempPts.length > 2) {\r\n                    \/\/ Una sola iteraci\u00f3n para l\u00edneas de campo (m\u00e1s fiel a la direcci\u00f3n)\r\n                    let newPts = [];\r\n                    newPts.push(tempPts[0]);\r\n                    for (let i = 0; i < tempPts.length - 1; i++) {\r\n                        let p0 = tempPts[i];\r\n                        let p1 = tempPts[i+1];\r\n                        let q = { x: 0.75 * p0.x + 0.25 * p1.x, y: 0.75 * p0.y + 0.25 * p1.y };\r\n                        let r = { x: 0.25 * p0.x + 0.75 * p1.x, y: 0.25 * p0.y + 0.75 * p1.y };\r\n                        newPts.push(q);\r\n                        newPts.push(r);\r\n                    }\r\n                    newPts.push(tempPts[tempPts.length - 1]);\r\n                    tempPts = newPts;\r\n                }\r\n\r\n                ctx.moveTo(tempPts[0].x, tempPts[0].y);\r\n                for(let i=1; i<tempPts.length; i++) {\r\n                    ctx.lineTo(tempPts[i].x, tempPts[i].y);\r\n                }\r\n                ctx.stroke();\r\n\r\n                \/\/ Flechas\r\n                \/\/ Calculamos \u00e1ngulos en los puntos originales para precisi\u00f3n\r\n                for(let i = 0; i < screenPoints.length - 1; i += 3) {\r\n                    let p1 = screenPoints[i];\r\n                    let p2 = screenPoints[i+1];\r\n                    let angle = Math.atan2(p2.y - p1.y, p2.x - p1.x);\r\n                    \/\/ Dibujar flecha en el punto intermedio suavizado si existe\r\n                    let targetP = tempPts[Math.floor(tempPts.length * (i \/ screenPoints.length))];\r\n                    if(targetP) drawArrowhead(targetP.x, targetP.y, angle, 8, '#fb923c');\r\n                }\r\n            }\r\n            ctx.lineWidth = 1;\r\n        }\r\n\r\n        function drawArrowhead(x, y, angle, size, color) {\r\n            ctx.save();\r\n            ctx.translate(x, y);\r\n            ctx.rotate(angle);\r\n            ctx.fillStyle = color;\r\n            ctx.beginPath();\r\n            ctx.moveTo(0, 0);\r\n            ctx.lineTo(-size, -size\/2);\r\n            ctx.lineTo(-size, size\/2);\r\n            ctx.closePath();\r\n            ctx.fill();\r\n            ctx.restore();\r\n        }\r\n\r\n        function drawProbe() {\r\n            let p = worldToScreen(state.probe.x, state.probe.y);\r\n            let grd = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, 30);\r\n            grd.addColorStop(0, 'rgba(34, 211, 238, 0.4)'); grd.addColorStop(1, 'transparent');\r\n            ctx.fillStyle = grd; ctx.beginPath(); ctx.arc(p.x, p.y, 30, 0, Math.PI*2); ctx.fill();\r\n            ctx.strokeStyle = 'rgba(34, 211, 238, 0.9)'; ctx.lineWidth = 1;\r\n            ctx.beginPath();\r\n            ctx.moveTo(p.x-20, p.y); ctx.lineTo(p.x-8, p.y); ctx.moveTo(p.x+8, p.y); ctx.lineTo(p.x+20, p.y);\r\n            ctx.moveTo(p.x, p.y-20); ctx.lineTo(p.x, p.y-8); ctx.moveTo(p.x, p.y+8); ctx.lineTo(p.x, p.y+20);\r\n            ctx.stroke();\r\n            ctx.fillStyle = '#22d3ee'; ctx.beginPath(); ctx.arc(p.x, p.y, 4, 0, Math.PI*2); ctx.fill();\r\n        }\r\n\r\n        \/\/ ==================== LOGICA DE GENERACION ====================\r\n        \r\n        function sortPointsNearestNeighbor(points) {\r\n            if (points.length <= 1) return points;\r\n            let sorted = [];\r\n            let remaining = [...points];\r\n            let current = remaining.shift();\r\n            sorted.push(current);\r\n            while (remaining.length > 0) {\r\n                let nearestIndex = 0;\r\n                let minDist = Infinity;\r\n                for (let i = 0; i < remaining.length; i++) {\r\n                    let d = (current.x - remaining[i].x)**2 + (current.y - remaining[i].y)**2;\r\n                    if (d < minDist) { minDist = d; nearestIndex = i; }\r\n                }\r\n                current = remaining.splice(nearestIndex, 1)[0];\r\n                sorted.push(current);\r\n            }\r\n            return sorted;\r\n        }\r\n\r\n        function generateCurvesFromPoints() {\r\n            let activePoints = state.allPoints.filter(p => !p.used);\r\n            if (activePoints.length < MIN_POINTS_CURVE) {\r\n                alert(`Necesita seleccionar al menos ${MIN_POINTS_CURVE} puntos para trazar una curva. Puntos actuales: ${activePoints.length}.`);\r\n                return;\r\n            }\r\n            \r\n            let clusters = {};\r\n            let tolerance = 0.5; \r\n            activePoints.forEach(p => {\r\n                let keys = Object.keys(clusters).map(Number);\r\n                let found = false;\r\n                for (let k of keys) { if (Math.abs(p.V - k) < tolerance) { clusters[k].push(p); found = true; break; } }\r\n                if (!found) clusters[p.V] = [p];\r\n            });\r\n\r\n            let colors = ['#fbbf24', '#a78bfa', '#f472b6', '#34d399', '#fb923c', '#38bdf8'];\r\n            let newCurvesCount = 0;\r\n            \r\n            for (let V in clusters) {\r\n                let points = clusters[V];\r\n                if (points.length >= MIN_POINTS_CURVE) { \r\n                    let sorted = sortPointsNearestNeighbor(points);\r\n                    let sumV = points.reduce((acc, p) => acc + p.V, 0);\r\n                    let avgV = sumV \/ points.length;\r\n                    \r\n                    state.userCurves.push({ \r\n                        potential: parseFloat(V), \r\n                        averagePotential: avgV, \r\n                        points: sorted, \r\n                        color: colors[state.userCurves.length % colors.length] \r\n                    });\r\n                    \r\n                    points.forEach(p => p.used = true);\r\n                    newCurvesCount++;\r\n                }\r\n            }\r\n            \r\n            if (newCurvesCount > 0) {\r\n                state.measuredPoints = state.allPoints.filter(p => !p.used);\r\n                updateTable(); updateStats(); updateGeneratedList();\r\n            } else {\r\n                alert(`No se encontraron grupos con suficientes puntos (m\u00ednimo ${MIN_POINTS_CURVE}).`);\r\n            }\r\n        }\r\n\r\n        function generateFieldLinesFromPoints() {\r\n            let activePoints = state.allPoints.filter(p => !p.used);\r\n            if (activePoints.length < MIN_POINTS_CURVE) {\r\n                alert(`Necesita seleccionar al menos ${MIN_POINTS_CURVE} puntos para trazar una l\u00ednea. Puntos actuales: ${activePoints.length}.`);\r\n                return;\r\n            }\r\n\r\n            let sorted = sortPointsNearestNeighbor(activePoints);\r\n\r\n            state.userFieldLines.push({\r\n                points: sorted\r\n            });\r\n\r\n            sorted.forEach(p => p.used = true);\r\n            state.measuredPoints = state.allPoints.filter(p => !p.used);\r\n            updateTable(); updateStats(); updateGeneratedList();\r\n        }\r\n\r\n        \/\/ ==================== ELIMINACI\u00d3N DE TRAZAS ====================\r\n        \r\n        function deleteCurve(index) {\r\n            let curve = state.userCurves[index];\r\n            if (!curve) return;\r\n\r\n            \/\/ Filtrar puntos de esta curva del registro total\r\n            let pointsToRemove = new Set(curve.points.map(p => p)); \/\/ Set de referencias\r\n            state.allPoints = state.allPoints.filter(p => !pointsToRemove.has(p));\r\n            \r\n            \/\/ Eliminar la curva\r\n            state.userCurves.splice(index, 1);\r\n            \r\n            \/\/ Actualizar puntos activos (por si acaso)\r\n            state.measuredPoints = state.allPoints.filter(p => !p.used);\r\n            \r\n            updateTable(); updateStats(); updateGeneratedList();\r\n        }\r\n\r\n        function deleteFieldLine(index) {\r\n            let line = state.userFieldLines[index];\r\n            if (!line) return;\r\n\r\n            \/\/ Filtrar puntos\r\n            let pointsToRemove = new Set(line.points.map(p => p));\r\n            state.allPoints = state.allPoints.filter(p => !pointsToRemove.has(p));\r\n            \r\n            \/\/ Eliminar la l\u00ednea\r\n            state.userFieldLines.splice(index, 1);\r\n            \r\n            \/\/ Actualizar\r\n            state.measuredPoints = state.allPoints.filter(p => !p.used);\r\n            \r\n            updateTable(); updateStats(); updateGeneratedList();\r\n        }\r\n\r\n        function updateGeneratedList() {\r\n            let list = document.getElementById('generatedList');\r\n            list.innerHTML = '';\r\n\r\n            if (state.userCurves.length === 0 && state.userFieldLines.length === 0) {\r\n                list.innerHTML = '<div class=\"text-xs text-center text-[var(--fg-muted)]\">Ninguna traza generada.<\/div>';\r\n                return;\r\n            }\r\n\r\n            \/\/ Listar Equipotenciales\r\n            state.userCurves.forEach((c, i) => {\r\n                let div = document.createElement('div');\r\n                div.className = 'generated-item';\r\n                div.innerHTML = `\r\n                    <span style=\"color:${c.color}\">Equipotencial ${i+1} (${c.averagePotential.toFixed(1)}V)<\/span>\r\n                    <button class=\"btn-delete-small\" onclick=\"deleteCurve(${i})\">Borrar<\/button>\r\n                `;\r\n                list.appendChild(div);\r\n            });\r\n\r\n            \/\/ Listar L\u00edneas de Campo\r\n            state.userFieldLines.forEach((l, i) => {\r\n                let div = document.createElement('div');\r\n                div.className = 'generated-item';\r\n                div.innerHTML = `\r\n                    <span style=\"color:#fb923c\">L. Campo ${i+1}<\/span>\r\n                    <button class=\"btn-delete-small\" onclick=\"deleteFieldLine(${i})\">Borrar<\/button>\r\n                `;\r\n                list.appendChild(div);\r\n            });\r\n        }\r\n\r\n        \/\/ ==================== INTERACTION ====================\r\n        \r\n        function handleMouseMove(e) {\r\n            const rect = canvas.getBoundingClientRect();\r\n            const world = screenToWorld(e.clientX - rect.left, e.clientY - rect.top);\r\n            state.probe.x = world.x; state.probe.y = world.y;\r\n            document.getElementById('probeX').textContent = world.x.toFixed(2);\r\n            document.getElementById('probeY').textContent = world.y.toFixed(2);\r\n            let V = state.powerOn ? calculatePotential(world.x, world.y) : 0;\r\n            document.getElementById('voltmeterDisplay').textContent = V.toFixed(2) + ' V';\r\n            if (state.draggingElectrode !== null) {\r\n                state.electrodes[state.draggingElectrode].x = world.x;\r\n                state.electrodes[state.draggingElectrode].y = world.y;\r\n                state.needsRecalculation = true;\r\n            }\r\n        }\r\n\r\n        function handleMouseDown(e) {\r\n            if (e.button === 0) {\r\n                const rect = canvas.getBoundingClientRect();\r\n                const world = screenToWorld(e.clientX - rect.left, e.clientY - rect.top);\r\n                let found = false;\r\n                for (let i = 0; i < state.electrodes.length; i++) {\r\n                    let el = state.electrodes[i];\r\n                    let dist = Math.sqrt((world.x - el.x)**2 + (world.y - el.y)**2);\r\n                    let hitRadius = el.type === 'plate' ? 1.0 : (el.radius || 0.5) + 0.3;\r\n                    if (dist < hitRadius) { state.draggingElectrode = i; canvas.style.cursor = 'grabbing'; found = true; break; }\r\n                }\r\n                if (!found) addPoint(world.x, world.y);\r\n            }\r\n        }\r\n\r\n        function handleRightClick(e) {\r\n            e.preventDefault();\r\n            const rect = canvas.getBoundingClientRect();\r\n            const world = screenToWorld(e.clientX - rect.left, e.clientY - rect.top);\r\n            deleteNearestPoint(world.x, world.y);\r\n        }\r\n\r\n        function handleWheel(e) {\r\n            e.preventDefault();\r\n            const delta = e.deltaY > 0 ? 0.9 : 1.1;\r\n            state.view.zoom = Math.max(0.5, Math.min(5, state.view.zoom * delta));\r\n            document.getElementById('zoomLevel').textContent = Math.round(state.view.zoom * 100);\r\n            state.needsRecalculation = true;\r\n        }\r\n\r\n        \/\/ ==================== UI ACTIONS ====================\r\n        \r\n        function addPoint(x, y) {\r\n            let V = calculatePotential(x, y);\r\n            let newPoint = { x, y, V, used: false };\r\n            state.allPoints.push(newPoint);\r\n            state.measuredPoints.push(newPoint);\r\n            updateTable(); updateStats();\r\n        }\r\n\r\n        function deleteNearestPoint(x, y) {\r\n            if (state.measuredPoints.length === 0) return;\r\n            let minD = Infinity, idxActive = -1;\r\n            state.measuredPoints.forEach((p, i) => {\r\n                let d = (p.x - x)**2 + (p.y - y)**2;\r\n                if (d < minD) { minD = d; idxActive = i; }\r\n            });\r\n            if (idxActive !== -1 && minD < 2.0) { \r\n                let pointToRemove = state.measuredPoints[idxActive];\r\n                state.measuredPoints.splice(idxActive, 1);\r\n                let idxInAll = state.allPoints.indexOf(pointToRemove);\r\n                if (idxInAll > -1) state.allPoints.splice(idxInAll, 1);\r\n                updateTable(); updateStats();\r\n            }\r\n        }\r\n\r\n        function updateTable() {\r\n            let tbody = document.getElementById('pointsTable');\r\n            tbody.innerHTML = '';\r\n            state.allPoints.forEach((p, i) => {\r\n                let tr = document.createElement('tr');\r\n                tr.className = p.used ? \"used-point-row\" : \"\";\r\n                tr.innerHTML = `<td class=\"py-1 px-2\">${i+1}<\/td>\r\n                                <td class=\"text-right py-1 px-2\">${p.x.toFixed(2)}<\/td>\r\n                                <td class=\"text-right py-1 px-2\">${p.y.toFixed(2)}<\/td>\r\n                                <td class=\"text-right py-1 px-2\">${p.V.toFixed(2)}<\/td>`;\r\n                tbody.appendChild(tr);\r\n            });\r\n            let container = tbody.parentElement;\r\n            container.scrollTop = container.scrollHeight;\r\n        }\r\n\r\n        function updateStats() {\r\n            document.getElementById('totalPoints').textContent = state.measuredPoints.length;\r\n            document.getElementById('totalCurves').textContent = state.userCurves.length;\r\n            document.getElementById('totalFieldLines').textContent = state.userFieldLines.length;\r\n        }\r\n\r\n        function togglePower() {\r\n            state.powerOn = !state.powerOn;\r\n            let btn = document.getElementById('powerBtn');\r\n            if (state.powerOn) {\r\n                btn.textContent = 'Apagar'; btn.classList.add('active');\r\n                document.getElementById('powerLed').classList.add('on');\r\n                document.getElementById('statusLed').classList.add('on');\r\n                document.getElementById('statusLed').classList.remove('off');\r\n                document.getElementById('statusText').textContent = 'Sistema activo';\r\n                state.needsRecalculation = true;\r\n            } else {\r\n                btn.textContent = 'Encender'; btn.classList.remove('active');\r\n                document.getElementById('powerLed').classList.remove('on');\r\n                document.getElementById('statusLed').classList.remove('on');\r\n                document.getElementById('statusLed').classList.add('off');\r\n                document.getElementById('statusText').textContent = 'Sistema inactivo';\r\n            }\r\n        }\r\n\r\n        function invertPolarity() {\r\n            state.polarity *= -1;\r\n            state.electrodes.forEach(el => {\r\n                el.charge *= -1;\r\n                el.color = el.charge > 0 ? '#ef4444' : '#3b82f6';\r\n                el.label = el.charge > 0 ? '+' : '-';\r\n            });\r\n            state.needsRecalculation = true;\r\n        }\r\n\r\n        function loadConfiguration() {\r\n            const val = document.getElementById('configSelect').value;\r\n            state.electrodes = configurations[val] ? configurations[val]() : [];\r\n            state.needsRecalculation = true;\r\n        }\r\n\r\n        \/\/ Correcci\u00f3n Exportaci\u00f3n CSV\r\n        function exportCSV() {\r\n            let csv = 'ID,Tipo,X (cm),Y (cm),Potencial (V),Estado\\n';\r\n            \r\n            state.allPoints.forEach((p, i) => {\r\n                let status = p.used ? \"Fijado\" : \"Activo\";\r\n                csv += `${i+1},Punto,${p.x.toFixed(4)},${p.y.toFixed(4)},${p.V.toFixed(4)},${status}\\n`;\r\n            });\r\n\r\n            state.userCurves.forEach((c, ci) => {\r\n                csv += `# Equipotencial ${ci+1},Promedio: ${c.averagePotential.toFixed(2)} V\\n`;\r\n            });\r\n\r\n            state.userFieldLines.forEach((l, li) => {\r\n                csv += `# Linea de Campo ${li+1}\\n`;\r\n            });\r\n\r\n            \/\/ Crear Blob y enlace de descarga\r\n            let blob = new Blob([\"\\ufeff\" + csv], { type: 'text\/csv;charset=utf-8;' }); \/\/ BOM for Excel\r\n            let link = document.createElement(\"a\");\r\n            let url = URL.createObjectURL(blob);\r\n            link.setAttribute(\"href\", url);\r\n            link.setAttribute(\"download\", \"datos_laboratorio.csv\");\r\n            link.style.visibility = 'hidden';\r\n            document.body.appendChild(link);\r\n            link.click();\r\n            document.body.removeChild(link);\r\n        }\r\n\r\n        \/\/ Correcci\u00f3n Descarga Imagen\r\n        function downloadImage() {\r\n            \/\/ Asegurar renderizado actual\r\n            render();\r\n            \r\n            \/\/ Crear enlace temporal\r\n            let link = document.createElement('a');\r\n            link.download = 'cuba_electrolitica.png';\r\n            link.href = canvas.toDataURL('image\/png');\r\n            document.body.appendChild(link); \/\/ Firefox requirement\r\n            link.click();\r\n            document.body.removeChild(link);\r\n        }\r\n\r\n        function zoomIn() { state.view.zoom = Math.min(5, state.view.zoom*1.2); document.getElementById('zoomLevel').textContent = Math.round(state.view.zoom*100); state.needsRecalculation = true; }\r\n        function zoomOut() { state.view.zoom = Math.max(0.5, state.view.zoom\/1.2); document.getElementById('zoomLevel').textContent = Math.round(state.view.zoom*100); state.needsRecalculation = true; }\r\n        function resetView() { state.view = {zoom:1, offsetX:0, offsetY:0}; document.getElementById('zoomLevel').textContent='100'; state.needsRecalculation = true; }\r\n        \r\n        function resetSimulation() {\r\n            state.allPoints = [];\r\n            state.measuredPoints = [];\r\n            state.userCurves = [];\r\n            state.userFieldLines = [];\r\n            state.voltage = 0; state.powerOn = false;\r\n            document.getElementById('voltageSlider').value = 0;\r\n            document.getElementById('voltageDisplay').textContent = '0.00 V';\r\n            document.getElementById('powerBtn').textContent = 'Encender';\r\n            document.getElementById('powerBtn').classList.remove('active');\r\n            document.getElementById('powerLed').classList.remove('on');\r\n            document.getElementById('statusLed').classList.remove('on');\r\n            document.getElementById('statusLed').classList.add('off');\r\n            document.getElementById('statusText').textContent = 'Sistema inactivo';\r\n            loadConfiguration();\r\n            updateTable(); updateStats(); updateGeneratedList();\r\n        }\r\n\r\n        document.getElementById('voltageSlider').addEventListener('input', function() {\r\n            state.voltage = parseFloat(this.value);\r\n            document.getElementById('voltageDisplay').textContent = state.voltage.toFixed(2) + ' V';\r\n            state.needsRecalculation = true;\r\n        });\r\n    <\/script>\r\n    \r\n    \r\n    \r\n    <style>\r\n  .btn-guia {\r\n    position: fixed;\r\n    bottom: 24px;\r\n    left: 24px;\r\n    z-index: 999;\r\n    background: #3fb950;\r\n    color: #0d1117;\r\n    font-family: 'Space Grotesk', sans-serif;\r\n    font-size: 0.82rem;\r\n    font-weight: 700;\r\n    letter-spacing: 1px;\r\n    text-transform: uppercase;\r\n    padding: 10px 18px;\r\n    border-radius: 8px;\r\n    border: none;\r\n    cursor: pointer;\r\n    box-shadow: 0 4px 20px rgba(63,185,80,0.35);\r\n    transition: transform 0.2s, box-shadow 0.2s;\r\n    display: flex;\r\n    align-items: center;\r\n    gap: 8px;\r\n  }\r\n  .btn-guia:hover {\r\n    transform: translateY(-2px);\r\n    box-shadow: 0 6px 24px rgba(63,185,80,0.5);\r\n  }\r\n  .btn-guia svg { width: 15px; height: 15px; flex-shrink: 0; }\r\n  .guia-panel {\r\n    position: fixed;\r\n    bottom: 72px;\r\n    left: 24px;\r\n    width: 400px;\r\n    max-height: 70vh;\r\n    z-index: 1000;\r\n    background: #ffffff;\r\n    border: 1px solid #d0d7de;\r\n    border-radius: 12px;\r\n    box-shadow: 0 16px 48px rgba(0,0,0,0.4);\r\n    display: none;\r\n    flex-direction: column;\r\n    overflow: hidden;\r\n    font-family: 'Space Grotesk', sans-serif;\r\n  }\r\n  .guia-panel.visible { display: flex; }\r\n  .guia-header {\r\n    display: flex;\r\n    justify-content: space-between;\r\n    align-items: center;\r\n    padding: 12px 16px;\r\n    border-bottom: 1px solid #d0d7de;\r\n    flex-shrink: 0;\r\n    background: #f6f8fa;\r\n  }\r\n  .guia-header-title {\r\n    display: flex;\r\n    align-items: center;\r\n    gap: 8px;\r\n    font-size: 0.85rem;\r\n    font-weight: 700;\r\n    color: #1f2328;\r\n  }\r\n  .guia-header-title svg { width: 15px; height: 15px; color: #3fb950; }\r\n  .guia-close {\r\n    background: none;\r\n    border: none;\r\n    color: #636c76;\r\n    font-size: 1rem;\r\n    cursor: pointer;\r\n    padding: 2px 7px;\r\n    border-radius: 4px;\r\n    transition: color 0.2s, background 0.2s;\r\n    line-height: 1;\r\n  }\r\n  .guia-close:hover { color: #1f2328; background: rgba(0,0,0,0.06); }\r\n  .guia-body {\r\n    overflow-y: auto;\r\n    padding: 18px 18px 20px;\r\n    color: #1f2328;\r\n    font-size: 0.83rem;\r\n    line-height: 1.65;\r\n    background: #ffffff;\r\n  }\r\n  .guia-body::-webkit-scrollbar { width: 5px; }\r\n  .guia-body::-webkit-scrollbar-track { background: transparent; }\r\n  .guia-body::-webkit-scrollbar-thumb { background: #d0d7de; border-radius: 3px; }\r\n  .guia-section-title {\r\n    font-size: 1rem;\r\n    font-weight: 700;\r\n    color: #1f2328;\r\n    margin: 0 0 12px 0;\r\n    padding-bottom: 8px;\r\n    border-bottom: 1px solid #d0d7de;\r\n  }\r\n  .guia-block { margin-bottom: 14px; }\r\n  .guia-label {\r\n    font-size: 0.72rem;\r\n    text-transform: uppercase;\r\n    letter-spacing: 1px;\r\n    color: #636c76;\r\n    margin-bottom: 4px;\r\n  }\r\n  .guia-text { color: #1f2328; }\r\n  .guia-formula {\r\n    background: #f6f8fa;\r\n    border: 1px solid #d0d7de;\r\n    border-radius: 6px;\r\n    padding: 10px 14px;\r\n    text-align: center;\r\n    font-family: 'JetBrains Mono', monospace;\r\n    font-size: 0.88rem;\r\n    color: #0550ae;\r\n    margin: 10px 0;\r\n  }\r\n  .guia-steps { list-style: none; margin: 0; padding: 0; counter-reset: steps; }\r\n  .guia-steps li {\r\n    counter-increment: steps;\r\n    display: flex;\r\n    gap: 10px;\r\n    margin-bottom: 8px;\r\n    align-items: flex-start;\r\n    color: #1f2328;\r\n  }\r\n  .guia-steps li::before {\r\n    content: counter(steps);\r\n    background: #dafbe1;\r\n    color: #1a7f37;\r\n    font-size: 0.72rem;\r\n    font-weight: 700;\r\n    width: 20px;\r\n    height: 20px;\r\n    border-radius: 50%;\r\n    display: flex;\r\n    align-items: center;\r\n    justify-content: center;\r\n    flex-shrink: 0;\r\n    margin-top: 1px;\r\n    border: 1px solid #1a7f37;\r\n  }\r\n  .guia-result {\r\n    background: #dafbe1;\r\n    border-left: 3px solid #1a7f37;\r\n    border-radius: 0 6px 6px 0;\r\n    padding: 10px 14px;\r\n    color: #1f2328;\r\n    font-size: 0.82rem;\r\n    margin-top: 10px;\r\n  }\r\n  .guia-params {\r\n    background: #f6f8fa;\r\n    border: 1px solid #d0d7de;\r\n    border-radius: 6px;\r\n    padding: 10px 14px;\r\n    font-size: 0.8rem;\r\n    color: #1f2328;\r\n  }\r\n  .guia-params span { color: #0550ae; font-family: 'JetBrains Mono', monospace; }\r\n  .var { font-family: 'JetBrains Mono', monospace; }\r\n  .var.pos { color: #cf222e; }\r\n  .var.neg { color: #0550ae; }\r\n  .var.neu { color: #7d4e00; }\r\n  .var.ok  { color: #1a7f37; }\r\n<\/style>\r\n\r\n<div class=\"guia-panel\" id=\"guiaPanel\">\r\n  <div class=\"guia-header\">\r\n    <div class=\"guia-header-title\">\r\n      <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z\"\/><path d=\"M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z\"\/><\/svg>\r\n      Gu\u00eda de uso\r\n    <\/div>\r\n    <button class=\"guia-close\" onclick=\"toggleGuia()\">\u2715<\/button>\r\n  <\/div>\r\n\r\n  <div class=\"guia-body\">\r\n\r\n    <!--\u2605 BORRAR DESDE AQUI \u2605-->\r\n\r\n\r\n<div style=\"font-family: Arial, sans-serif; line-height: 1.6; color: #333;\">\r\n<h3 style=\"color: #0056b3;\"><strong><span style=\"font-size: 16px; color: #000080;\">Gr\u00e1fico de Superficies Equipotenciales y L\u00edneas de Campo<\/span><\/strong><strong><span style=\"font-size: 16px; color: #000080;\">     <\/span><span style=\"font-size: 16px; color: #ff0000;\"> <\/span><\/strong><\/h3>\r\n<span style=\"font-size: 16px; color: #000000;\"><strong>Objetivo:<\/strong> Trazar y analizar la configuraci\u00f3n espacial de las l\u00edneas de campo el\u00e9ctrico y las superficies equipotenciales para diferentes distribuciones de carga puntual, comprendiendo su relaci\u00f3n de ortogonalidad.<\/span>\r\n\r\n<span style=\"font-size: 16px; color: #000000;\"><strong>Descripci\u00f3n breve:<\/strong> Herramienta interactiva que permite al usuario \"dibujar\" el mapa electrost\u00e1tico de un sistema. Al colocar cargas en el plano, la simulaci\u00f3n genera las l\u00edneas de fuerza que indican la direcci\u00f3n del campo \\(\\mathbf{E}\\) y permite identificar los puntos de igual potencial \\(V\\), demostrando que el trabajo necesario para mover una carga sobre una superficie equipotencial es nulo.<\/span>\r\n<div style=\"text-align: center; margin: 20px 0;\"><span style=\"font-size: 16px; color: #000000;\">\\( W = -q \\cdot \\Delta V = 0 \\text{ (sobre una equipotencial)} \\)<\/span><\/div>\r\n<span style=\"font-size: 16px; color: #000000;\"><strong>C\u00f3mo usar (pasos):<\/strong><\/span>\r\n<ul style=\"margin-left: 20px;\">\r\n \t<li><span style=\"font-size: 16px; color: #000000;\">Arrastrar cargas positivas y negativas al \u00e1rea de trabajo para crear configuraciones como dipolos, cuadripolos o cargas lineales.<\/span><\/li>\r\n \t<li><span style=\"font-size: 16px; color: #000000;\">Utilizar la herramienta de \"Sensor de Potencial\" para marcar puntos en el espacio; la simulaci\u00f3n trazar\u00e1 autom\u00e1ticamente la l\u00ednea equipotencial que pasa por ese punto.<\/span><\/li>\r\n \t<li><span style=\"font-size: 16px; color: #000000;\">Activar la superposici\u00f3n de \"L\u00edneas de Campo\" para observar c\u00f3mo estas parten de las cargas positivas y llegan a las negativas.<\/span><\/li>\r\n \t<li><span style=\"font-size: 16px; color: #000000;\">Verificar con la herramienta de \u00e1ngulo que en cada intersecci\u00f3n, la l\u00ednea de campo y la equipotencial forman \\(90^\\circ\\).<\/span><\/li>\r\n \t<li><span style=\"font-size: 16px; color: #000000;\">Limpiar el lienzo para probar configuraciones de cargas de igual signo y observar los puntos de equilibrio (donde el campo es nulo).<\/span><\/li>\r\n<\/ul>\r\n<span style=\"font-size: 16px; color: #000000;\"><strong>Par\u00e1metros ajustables:<\/strong> Valor de las cargas (\\(q\\)), posici\u00f3n de las fuentes, densidad de l\u00edneas de campo, precisi\u00f3n del trazado de equipotenciales y visualizaci\u00f3n de valores num\u00e9ricos de voltaje.<\/span>\r\n<p style=\"background-color: #f8f9fa; padding: 15px; border-left: 5px solid #0056b3;\"><span style=\"font-size: 16px; color: #000000;\"><strong>Resultados esperados \/ observaciones:<\/strong> Se debe observar que las superficies equipotenciales nunca se cruzan entre s\u00ed y que son m\u00e1s densas en las regiones donde el campo el\u00e9ctrico es m\u00e1s intenso. La simulaci\u00f3n confirma que las l\u00edneas de campo son siempre normales a las equipotenciales en todo punto del espacio.<\/span><\/p>\r\n\r\n<\/div>\r\n\r\n   <!--\u2605 BORRAR HASTA AQUI \u2605-->\r\n\r\n  <\/div>\r\n<\/div>\r\n\r\n<button class=\"btn-guia\" onclick=\"toggleGuia()\">\r\n  <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z\"\/><path d=\"M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z\"\/><\/svg>\r\n  Gu\u00eda de uso\r\n<\/button>\r\n\r\n<script>\r\n  function toggleGuia() {\r\n    document.getElementById('guiaPanel').classList.toggle('visible');\r\n  }\r\n<\/script>\r\n\r\n    \r\n    \r\n    \r\n    \r\n    \r\n    \r\n    \r\n    \r\n<\/body>\r\n<\/html>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Laboratorio Virtual &#8211; Cuba Electrolitica Laboratorio Virtual &#8211; Cuba Electrolitica FICA &#8211; UNSL Simulacion interactiva de superficies equipotenciales y lineas de campo Sistema inactivo Reiniciar&hellip;<\/p>\n","protected":false},"author":7,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"class_list":["post-6107","page","type-page","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.3 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>graficar_sup_equipotencial_lin_campo - F\u00edsica 2<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/fisica2.fica.unsl.edu.ar\/index.php\/graficar_sup_equipotencial_lin_campo\/\" \/>\n<meta property=\"og:locale\" content=\"es_ES\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"graficar_sup_equipotencial_lin_campo - F\u00edsica 2\" \/>\n<meta property=\"og:description\" content=\"Laboratorio Virtual &#8211; Cuba Electrolitica Laboratorio Virtual &#8211; Cuba Electrolitica FICA &#8211; UNSL Simulacion interactiva de superficies equipotenciales y lineas de campo Sistema inactivo Reiniciar&hellip;\" \/>\n<meta property=\"og:url\" content=\"https:\/\/fisica2.fica.unsl.edu.ar\/index.php\/graficar_sup_equipotencial_lin_campo\/\" \/>\n<meta property=\"og:site_name\" content=\"F\u00edsica 2\" \/>\n<meta property=\"article:modified_time\" content=\"2026-04-18T21:25:42+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Tiempo de lectura\" \/>\n\t<meta name=\"twitter:data1\" content=\"3 minutos\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/fisica2.fica.unsl.edu.ar\\\/index.php\\\/graficar_sup_equipotencial_lin_campo\\\/\",\"url\":\"https:\\\/\\\/fisica2.fica.unsl.edu.ar\\\/index.php\\\/graficar_sup_equipotencial_lin_campo\\\/\",\"name\":\"graficar_sup_equipotencial_lin_campo - F\u00edsica 2\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/fisica2.fica.unsl.edu.ar\\\/#website\"},\"datePublished\":\"2026-03-13T17:35:30+00:00\",\"dateModified\":\"2026-04-18T21:25:42+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/fisica2.fica.unsl.edu.ar\\\/index.php\\\/graficar_sup_equipotencial_lin_campo\\\/#breadcrumb\"},\"inLanguage\":\"es-AR\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/fisica2.fica.unsl.edu.ar\\\/index.php\\\/graficar_sup_equipotencial_lin_campo\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/fisica2.fica.unsl.edu.ar\\\/index.php\\\/graficar_sup_equipotencial_lin_campo\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Inicio\",\"item\":\"https:\\\/\\\/fisica2.fica.unsl.edu.ar\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"graficar_sup_equipotencial_lin_campo\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/fisica2.fica.unsl.edu.ar\\\/#website\",\"url\":\"https:\\\/\\\/fisica2.fica.unsl.edu.ar\\\/\",\"name\":\"F\u00edsica 2\",\"description\":\"FICA - UNSL\",\"publisher\":{\"@id\":\"https:\\\/\\\/fisica2.fica.unsl.edu.ar\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/fisica2.fica.unsl.edu.ar\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"es-AR\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/fisica2.fica.unsl.edu.ar\\\/#organization\",\"name\":\"SAC- Secretar\u00eda General FICA\",\"url\":\"https:\\\/\\\/fisica2.fica.unsl.edu.ar\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"es-AR\",\"@id\":\"https:\\\/\\\/fisica2.fica.unsl.edu.ar\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/fisica2.fica.unsl.edu.ar\\\/wp-content\\\/uploads\\\/2021\\\/11\\\/SG-Logo.png\",\"contentUrl\":\"https:\\\/\\\/fisica2.fica.unsl.edu.ar\\\/wp-content\\\/uploads\\\/2021\\\/11\\\/SG-Logo.png\",\"width\":4483,\"height\":1231,\"caption\":\"SAC- Secretar\u00eda General FICA\"},\"image\":{\"@id\":\"https:\\\/\\\/fisica2.fica.unsl.edu.ar\\\/#\\\/schema\\\/logo\\\/image\\\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"graficar_sup_equipotencial_lin_campo - F\u00edsica 2","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/fisica2.fica.unsl.edu.ar\/index.php\/graficar_sup_equipotencial_lin_campo\/","og_locale":"es_ES","og_type":"article","og_title":"graficar_sup_equipotencial_lin_campo - F\u00edsica 2","og_description":"Laboratorio Virtual &#8211; Cuba Electrolitica Laboratorio Virtual &#8211; Cuba Electrolitica FICA &#8211; UNSL Simulacion interactiva de superficies equipotenciales y lineas de campo Sistema inactivo Reiniciar&hellip;","og_url":"https:\/\/fisica2.fica.unsl.edu.ar\/index.php\/graficar_sup_equipotencial_lin_campo\/","og_site_name":"F\u00edsica 2","article_modified_time":"2026-04-18T21:25:42+00:00","twitter_card":"summary_large_image","twitter_misc":{"Tiempo de lectura":"3 minutos"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/fisica2.fica.unsl.edu.ar\/index.php\/graficar_sup_equipotencial_lin_campo\/","url":"https:\/\/fisica2.fica.unsl.edu.ar\/index.php\/graficar_sup_equipotencial_lin_campo\/","name":"graficar_sup_equipotencial_lin_campo - F\u00edsica 2","isPartOf":{"@id":"https:\/\/fisica2.fica.unsl.edu.ar\/#website"},"datePublished":"2026-03-13T17:35:30+00:00","dateModified":"2026-04-18T21:25:42+00:00","breadcrumb":{"@id":"https:\/\/fisica2.fica.unsl.edu.ar\/index.php\/graficar_sup_equipotencial_lin_campo\/#breadcrumb"},"inLanguage":"es-AR","potentialAction":[{"@type":"ReadAction","target":["https:\/\/fisica2.fica.unsl.edu.ar\/index.php\/graficar_sup_equipotencial_lin_campo\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/fisica2.fica.unsl.edu.ar\/index.php\/graficar_sup_equipotencial_lin_campo\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Inicio","item":"https:\/\/fisica2.fica.unsl.edu.ar\/"},{"@type":"ListItem","position":2,"name":"graficar_sup_equipotencial_lin_campo"}]},{"@type":"WebSite","@id":"https:\/\/fisica2.fica.unsl.edu.ar\/#website","url":"https:\/\/fisica2.fica.unsl.edu.ar\/","name":"F\u00edsica 2","description":"FICA - UNSL","publisher":{"@id":"https:\/\/fisica2.fica.unsl.edu.ar\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/fisica2.fica.unsl.edu.ar\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"es-AR"},{"@type":"Organization","@id":"https:\/\/fisica2.fica.unsl.edu.ar\/#organization","name":"SAC- Secretar\u00eda General FICA","url":"https:\/\/fisica2.fica.unsl.edu.ar\/","logo":{"@type":"ImageObject","inLanguage":"es-AR","@id":"https:\/\/fisica2.fica.unsl.edu.ar\/#\/schema\/logo\/image\/","url":"https:\/\/fisica2.fica.unsl.edu.ar\/wp-content\/uploads\/2021\/11\/SG-Logo.png","contentUrl":"https:\/\/fisica2.fica.unsl.edu.ar\/wp-content\/uploads\/2021\/11\/SG-Logo.png","width":4483,"height":1231,"caption":"SAC- Secretar\u00eda General FICA"},"image":{"@id":"https:\/\/fisica2.fica.unsl.edu.ar\/#\/schema\/logo\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/fisica2.fica.unsl.edu.ar\/index.php\/wp-json\/wp\/v2\/pages\/6107","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fisica2.fica.unsl.edu.ar\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/fisica2.fica.unsl.edu.ar\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/fisica2.fica.unsl.edu.ar\/index.php\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/fisica2.fica.unsl.edu.ar\/index.php\/wp-json\/wp\/v2\/comments?post=6107"}],"version-history":[{"count":76,"href":"https:\/\/fisica2.fica.unsl.edu.ar\/index.php\/wp-json\/wp\/v2\/pages\/6107\/revisions"}],"predecessor-version":[{"id":7112,"href":"https:\/\/fisica2.fica.unsl.edu.ar\/index.php\/wp-json\/wp\/v2\/pages\/6107\/revisions\/7112"}],"wp:attachment":[{"href":"https:\/\/fisica2.fica.unsl.edu.ar\/index.php\/wp-json\/wp\/v2\/media?parent=6107"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}