Sobre estudiantes de primer grado, aprendizaje a distancia y programación asincrónica

imagen



Soy un reductor de cambios. Dio la casualidad de que durante los últimos tres años mi esposa y yo hemos estado disfrutando del paisaje rural fuera de la ventana, el aire fresco y el canto de los pájaros. Las comodidades en la casa, Internet óptico de un proveedor local, una poderosa fuente de alimentación ininterrumpida y un covid que apareció de repente, hicieron que la idea de mudarse de una metrópoli no fuera tan extraña.



Mientras me dedicaba con entusiasmo al desarrollo web, en algún lugar del fondo, mi esposa se quejaba periódicamente de los problemas de elegir una escuela para mi hijo. Y luego (de repente) el niño creció y la pregunta de la escuela se mantuvo firme. Bien, entonces ha llegado el momento. Vamos a resolverlo juntos, ¿qué está mal con el sistema educativo en el antiguo 1/6 de la tierra y qué podemos hacer al respecto?



Dejaré los métodos tradicionales de enseñanza presencial fuera del alcance de este artículo. Solo diré que las escuelas ordinarias tienen tanto ventajas indiscutibles como serias desventajas, a las que, por cierto, recientemente se ha sumado el autoaislamiento forzado. Aquí analizamos las opciones de educación a distancia y familiar que, por diversas razones, han atraído recientemente a más padres.



Para ser claros: aprendizaje a distancia significa clases en una escuela regular con la ayuda de "tecnologías de aprendizaje a distancia" (DOT), y familia significa abandonar voluntariamente la escuela y aprender solo por la familia (de hecho, esta es una buena pasantía). Sin embargo, en cualquier caso, el niño debe estar adscrito a alguna de las escuelas disponibles, al menos para aprobar las certificaciones intermedias.



Y ahora algunas observaciones de la vida. Con el traslado forzado a la educación a distancia de niños que ya han estudiado en una escuela regular, todo es triste. Los escolares perciben este regalo del destino como una especie de vacaciones, los padres no están acostumbrados a seguir la disciplina durante las clases y, como resultado, el rendimiento académico general cae inevitablemente.



Con los niños de primer grado, especialmente en el caso de la forma familiar, los padres, quizás, tienen la oportunidad de poner al niño “en los rieles” usando el interés natural y el efecto de la novedad. Para mí, personalmente, lograr la independencia es la tarea principal. Sentado y haciendo la tarea con un niño, considero el colmo de la estupidezno del todo razonable. Por supuesto, si quieres que tus hijos logren algo en la vida y no te cuelguen del cuello. Quiero, por tanto, mi objetivo es enseñar a un niño a aprender, a hacer preguntas correctamente y, en general, a pensar con su propia cabeza.



Llegar al punto. Elegir una escuela pública



Quizás, me gusta más la educación familiar por la oportunidad de elegir un programa y un horario de capacitación. Y puedes asistir físicamente a la escuela con menos frecuencia. Pero debe elegir una escuela pública, hablar con el director sobre la ubicación del niño y recibir una orden de admisión al primer grado al final del invierno para que no haya sorpresas en septiembre. Si bien, desde un punto de vista legal, la ley de educación no parece exigir certificaciones anuales, los "plazos", en mi experiencia, son excelentes motivadores, así que que haya certificaciones. Es poco probable que alguna escuela nos acepte con los brazos abiertos, pero estoy seguro de que encontraremos una opción digna en la ciudad más cercana.



Elegir un plan de estudios



Elegimos exactamente. Tratar de redactar un programa por su cuenta sin tener una educación especializada no es razonable. Aunque existen recursos educativos gubernamentales como la Russian Electronic School ( NES ) y la Moscow Electronic School ( MES ), que en teoría serían suficientes, pero ... Ambas opciones brindan planes de lecciones, videos, exámenes y tutoriales. Lo que no pude encontrar fueron los libros de texto en sí, ni siquiera para el plan de estudios obligatorio.



Y aquí falta lo más importante: la comunicación. Enseñarle a un niño mostrándole videos interminables y obligándolo a marcar las pruebas no funcionará. Esto significa que debe realizar lecciones de forma completamente independiente o elegir una de las escuelas en línea.



Elegir una escuela en línea



Casi hemos vuelto a donde empezamos. ¿Control remoto? Bien, echémosle un vistazo más de cerca. ¿Cómo se puede organizar el proceso educativo de forma remota? Esto plantea muchas preguntas, solo plantearé las claves:



* Comunicación en vivo. ¿Qué ofrecen las escuelas? Skype, Tims en el mejor de los casos. ¿Lecciones por Skype? De Verdad? Si no me equivoco, es 2020. ¿Abrir varias ventanas con hermosos botones multicolores frente al niño de primer grado y esperar a que no los presione, pero escuchará obedientemente a un tío o tía aburrido durante medio día? Nunca había visto niños así. ¿Y usted?



* Deberes. Más precisamente, ¿cómo llega al profesor para la prueba? De hecho, esta es una pregunta realmente difícil, quizás ni siquiera resuelta en principio. Opciones existentes:



  1. , . --, , , , - .

  2. . , - .

  3. . , .

  4. . , , , , ? . , , .

  5. . , , . , , , . , . . , , , , . .



* Estimados. Obviamente, las calificaciones dadas en la lección y al revisar la tarea deben caer en un diario electrónico disponible para los padres. Y llegan allí. Pero no de inmediato. Les pregunté a los niños mayores que se graduaron de uno de los prestigiosos liceos con una cúpula dorada (irónicamente, con un sesgo informativo), ¿por qué? La respuesta, para ser sincero, me sorprendió. Resulta que los maestros escriben las calificaciones en una hoja de papel y, después de las lecciones, las llevan a este diario electrónico en el portal estatal. Y esto mientras Tesla Elon Musk ara la inmensidad del espacio ...



Está bien, ¿es hora de hacer una pequeña investigación técnica y comprobar si existen razones objetivas para este estado de cosas?



Definamos los requisitos para una plataforma de aprendizaje ideal hipotética. De hecho, todo es simple: los niños deben permanecer en la lección, enfocándose en lo que dice y mostrando el maestro, respondiendo preguntas si es necesario y levantando la mano si lo desean. Básicamente, queremos una ventana de pantalla completa con una transmisión de la cámara, presentación o pizarra del profesor. La forma más sencilla de lograrlo es utilizar la tecnología WebRTC(comunicaciones en tiempo real, comunicaciones en tiempo real). Esta cosa funciona en cualquier navegador más o menos moderno, no requiere la compra de equipos adicionales y, además, proporciona una conexión de buena calidad. Y sí, este estándar requiere programación asincrónica al menos porque el método JS requerido navigator.mediaDevices.getUserMedia () devuelve una promesa . Todo parece estar claro, lo estoy empezando a implementar.



Digresión lírica sobre la elección de un marco
, «» JavaScript , . jQuery. , JS :



//  
element = $(selector);
element = document.querySelector(selector);

//    
element2 = element.find(selector2);
element2 = element.querySelector(selector2);

//  
element.hide();  //   display: none
element.classList.add('hidden');

      
      





, CSS «hidden», , opacity transition, fadeIn/fadeOut CSS. , JS !



//   onClick
element.click(e => { ... });
element.onclick = (e) => { ...  }

//  
element.toggleClass(class_name);
element.classList.toggle(class_name);

//  div
div = $("<div>");
div = document.createElement("div");

//   div  element
// (  ,   )
element.append(div);
element.append(div);

      
      





. .. , JS , . , , «» JS !



WebRTC está diseñado para la comunicación directa entre navegadores, utilizando tecnología punto a punto (p2p). Sin embargo, para establecer esta conexión, los navegadores deben informarse entre sí de su intención de comunicarse. Esto requiere un servidor de alarmas .



Un ejemplo de una implementación básica de un chat de video simple usando la topología de "malla completa"
'use strict';

(function () {
    const selfView = document.querySelector('#self-view'),
        remoteMaster = document.querySelector('#remote-master'),
        remoteSlaves = document.querySelector('#remote-slaves');

    let localStream,
        selfStream = null,
        socket = null,
        selfId = null,
        connections = {};

    // ***********************
    // UserMedia & DOM methods
    // ***********************

    const init = async () => {
        try {
            let stream = await navigator.mediaDevices.getUserMedia({
                audio: true, video: {
                    width: { max: 640 }, height: { max: 480 }
                }
            });
            localStream = stream;

            selfStream = new MediaStream();

            stream.getVideoTracks().forEach(track => {
                selfStream.addTrack(track, stream); // track.kind == 'video'
            });
            selfView.querySelector('video').srcObject = selfStream;

        } catch (e) {
            document.querySelector('#self-view').innerHTML =
                '<i>     </i>';
            console.error('Local stream not found: ', e);
        }
        wsInit();
    }

    const createRemoteView = (id, username) => {
        let iDiv = document.querySelector('#pc' + id);
        if (!iDiv) {
            iDiv = document.createElement('div');
            iDiv.className = 'remote-view';
            iDiv.id = 'pc' + id;

            let iVideo = document.createElement('video');
            iVideo.setAttribute('autoplay', 'true');
            iVideo.setAttribute('playsinline', 'true');

            let iLabel = document.createElement('span');

            iDiv.append(iVideo);
            iDiv.append(iLabel);

            if (!remoteMaster.querySelector('video')) {
                remoteMaster.append(iDiv);
                iLabel.textContent = '';
            } else {
                remoteSlaves.append(iDiv);
                iLabel.textContent = username;
            }
            remoteMaster.style.removeProperty('display');
        }
    }

    // *******************************
    // Signaling (Web Socket) methods
    // *******************************

    const wsInit = () => {
        socket = new WebSocket(SIGNALING_SERVER_URL);

        socket.onopen = function (e) {
            log('[socket open]  ');
        }

        socket.onmessage = function (event) {
            log('[socket message]    ', event);

            wsHandle(event.data);
        }

        socket.onclose = function (event) {
            if (event.wasClean) {
                log('[close]   , ' +
                    `=${event.code} =${event.reason}`);
            } else {
                log('[socket close]  ', event);
            }
            clearInterval(socket.timer);
        }

        socket.onerror = function (error) {
            logError('[socket error]', error);
        }

        socket.timer = setInterval(() => {
            socket.send('heartbeat');
        }, 10000);
    }

    const wsHandle = async (data) => {
        if (!data) {
            return;
        }
        try {
            data = JSON.parse(data);
        } catch (e) {
            return;
        }

        switch (data.type) {
            case 'handshake':
                selfId = data.uid;
                if (!Object.keys(data.users).length) {
                    createRemoteView(selfId, '');
                    remoteMaster.querySelector('video').srcObject =
                        selfStream;
                    selfView.remove();
                    break;
                } else {
                    selfView.style.removeProperty('display');
                }
                for (let id in data.users) {
                    await pcCreate(id, data.users[id]);
                }
                break;
            case 'offer':
                await wsHandleOffer(data);
                break;
            case 'answer':
                await wsHandleAnswer(data)
                break;
            case 'candidate':
                await wsHandleICECandidate(data);
                break;
            default:
                break;
        }
    }

    const wsHandleOffer = async (data) => {
        let pc = null;

        if (!connections[data.src]) {
            await pcCreate(data.src, data.username);
        }

        pc = connections[data.src].pc;

        // We need to set the remote description to the received SDP offer
        // so that our local WebRTC layer knows how to talk to the caller.
        let desc = new RTCSessionDescription(data.sdp);

        pc.setRemoteDescription(desc).catch(error => {
            logError('handleOffer', error);
        });

        await pc.setLocalDescription(await pc.createAnswer());

        wsSend({
            type: 'answer',
            target: data.src,
            sdp: pc.localDescription
        });

        connections[data.src].pc = pc; // ???
    }

    const wsHandleAnswer = async (data) => {
        log('*** Call recipient has accepted our call, answer:', data);

        let pc = connections[data.src].pc;

        // Configure the remote description,
        // which is the SDP payload in our 'answer' message.

        let desc = new RTCSessionDescription(data.sdp);
        await pc.setRemoteDescription(desc).catch((error) => {
            logError('handleAnswer', error);
        });
    }

    const wsHandleICECandidate = async (data) => {
        let pc = connections[data.src].pc;

        let candidate = new RTCIceCandidate(data.candidate);

        log('*** Adding received ICE candidate', candidate);

        pc.addIceCandidate(candidate).catch(error => {
            logError('handleICECandidate', error);
        });
    }

    const wsSend = (data) => {
        if (socket.readyState !== WebSocket.OPEN) {
            return;
        }
        socket.send(JSON.stringify(data));
    }

    // ***********************
    // Peer Connection methods
    // ***********************

    const pcCreate = async (id, username) => {
        if (connections[id]) {
            return;
        }
        try {
            let pc = new RTCPeerConnection(PC_CONFIG);

            pc.onicecandidate = (event) =>
                pcOnIceCandidate(event, id);
            pc.oniceconnectionstatechange = (event) =>
                pcOnIceConnectionStateChange(event, id);
            pc.onsignalingstatechange =  (event) =>
                pcOnSignalingStateChangeEvent(event, id);
            pc.onnegotiationneeded = (event) =>
                pcOnNegotiationNeeded(event, id);
            pc.ontrack = (event) =>
                pcOnTrack(event, id);

            connections[id] = {
                pc: pc,
                username: username
            }

            if (localStream) {
                try {
                    localStream.getTracks().forEach(
                        (track) => connections[id].pc.addTransceiver(track, {
                            streams: [localStream]
                        })
                    );
                } catch (err) {
                    logError(err);
                }
            } else {
                // Start negotiation to listen remote stream only
                pcOnNegotiationNeeded(null, id);
            }
            createRemoteView(id, username);
        } catch (error) {
            logError('Peer: Connection failed', error);
        }
    }

    const pcOnTrack = (event, id) => {
        let iVideo = document.querySelector('#pc' + id + ' video');
        iVideo.srcObject = event.streams[0];
    }

    const pcOnIceCandidate = (event, id) => {
        let pc = connections[id].pc;

        if (event.candidate && pc.remoteDescription) {
            log('*** Outgoing ICE candidate: ' + event.candidate);
            wsSend({
                type: 'candidate',
                target: id,
                candidate: event.candidate
            });
        }
    }

    const pcOnNegotiationNeeded = async (event, id) => {
        let pc = connections[id].pc;
        try {
            const offer = await pc.createOffer();

            // If the connection hasn't yet achieved the "stable" state,
            // return to the caller. Another negotiationneeded event
            // will be fired when the state stabilizes.
            if (pc.signalingState != 'stable') {
                return;
            }

            // Establish the offer as the local peer's current
            // description.
            await pc.setLocalDescription(offer);

            // Send the offer to the remote peer.
            wsSend({
                type: 'offer',
                target: id,
                sdp: pc.localDescription
            });
        } catch(err) {
            logError('*** The following error occurred while handling' +
                ' the negotiationneeded event:', err);
        };
    }

    const pcOnIceConnectionStateChange = (event, id) => {
        let pc = connections[id].pc;
        switch (pc.iceConnectionState) {
            case 'closed':
            case 'failed':
            case 'disconnected':
                pcClose(id);
                break;
        }
    }

    const pcOnSignalingStateChangeEvent = (event, id) => {
        let pc = connections[id].pc;

        log('*** WebRTC signaling state changed to: ' + pc.signalingState);

        switch (pc.signalingState) {
            case 'closed':
                pcClose(id);
                break;
        }
    }

    const pcClose = (id) => {
        let remoteView = document.querySelector('#pc' + id);

        if (connections[id]) {
            let pc = connections[id].pc;
            pc.close();
            delete connections[id];
        }
        if (remoteView) {
            remoteView.remove();
        }
    }

    // *******
    // Helpers
    // *******

    const log = (msg, data) => {
        if (!data) {
            data = ''
        }
        console.log(msg, data);
    }

    const logError = (msg, data) => {
        if (!data) {
            data = ''
        }
        console.error(msg, data);
    }

    init();
})();

      
      







El servidor de señalización se crea en el marco Python aiohttp y es una "vista" simple que procesa trivialmente las solicitudes WebRTC. La conexión al servidor en este ejemplo se realiza a través de sockets web . Bueno, además, los datos de chat de texto simple se transmiten a través del canal de señalización.



Ejemplo de implementación del servidor de señalización
import json
from aiohttp.web import WebSocketResponse, Response
from aiohttp import WSMsgType
from uuid import uuid1
from lib.views import BaseView


class WebSocket(BaseView):
    """ Process WS connections """

    async def get(self):
        username = self.request['current_user'].firstname or ''

        room_id = self.request.match_info.get('room_id')

        if room_id != 'test_room' and
            self.request['current_user'].is_anonymous:
            self.raise_error('forbidden')  # @TODO: send 4000

        if (self.request.headers.get('connection', '').lower() != 'upgrade' or
            self.request.headers.get('upgrade', '').lower() != 'websocket'):
            return Response(text=self.request.path)  # ???

        self.ws = WebSocketResponse()
        await self.ws.prepare(self.request)

        self.uid = str(uuid1())

        if room_id not in self.request.app['web_sockets']:
            self.request.app['web_sockets'][room_id] = {}

        self.room = self.request.app['web_sockets'][room_id]

        users = {}
        for id, data in self.room.items():
            users[id] = data['name']

        ip = self.request.headers.get(
            'X-FORWARDED-FOR',
            self.request.headers.get('X-REAL-IP',
            self.request.remote))

        msg = {
            'type': 'handshake',
            'uid': str(self.uid),
            'users': users, 'ip': ip}
        await self.ws.send_str(json.dumps(msg, ensure_ascii=False))

        self.room[self.uid] = {'name': username, 'ws': self.ws}

        try:
            async for msg in self.ws:
                if msg.type == WSMsgType.TEXT:
                    if msg.data == 'heartbeat':
                        print('---heartbeat---')
                        continue

                    try:
                        msg_data = json.loads(msg.data)

                        if 'target' not in msg_data or
                            msg_data['target'] not in self.room:
                            continue

                        msg_data['src'] = self.uid

                        if 'type' in msg_data and 'target' in msg_data:
                            if msg_data['type'] == 'offer':
                                msg_data['username'] = username
                        else:
                            print('INVALID DATA:', msg_data)
                    except Exception as e:
                        print('INVALID JSON', e, msg)

                    try:
                        await self.room[msg_data['target']]['ws'].send_json(
                            msg_data);
                    except Exception as e:
                        if 'target' in msg_data:
                            self.room.pop(msg_data['target'])

        finally:
            self.room.pop(self.uid)

        return self.ws

      
      







La tecnología WebRTC, además de la comunicación por video, le permite otorgar permiso al navegador para capturar el contenido de una pantalla o una aplicación separada, lo que puede ser indispensable al realizar lecciones en línea, seminarios web o presentaciones. Genial, usémoslo.



Me dejé llevar tanto por las posibilidades modernas de la comunicación por vídeo que casi me olvido del tema más importante de la clase: la pizarra interactiva. Sin embargo, la implementación básica es tan trivial que no sobrecargaré este artículo con ella. Simplemente agregamos el lienzo , escuchamos los eventos de movimiento del mouse onmousemove (ontouchmove para tabletas) y enviamos las coordenadas resultantes a todos los puntos conectados a través del mismo servidor de señalización.



Prueba de su pizarra digital interactiva



Aquí necesitas una tableta, un digitalizador y un niño vivo. Al mismo tiempo, comprobaremos la posibilidad de digitalizar la entrada de escritura a mano.



Para empezar, tomé una vieja tableta Galaxy Tab con Android 4.4, un lápiz óptico casero y los primeros cuadernos que encontré como fondo para el lienzo. No instalé programas adicionales. El resultado me desanimó: ¡mi tableta no es en absoluto adecuada para escribir! Es decir, no hay problema para mover el dedo a lo largo de él, pero colocar el lápiz en el contorno de una letra, incluso una tan grande como la de la imagen de abajo, ya es un problema. Además, el dispositivo comienza a embotarse en el proceso de dibujo, como resultado de lo cual las líneas se rompen. Además, no pude hacer que el niño no apoyara la muñeca en la pantalla, lo que deja un toque adicional a mano, y la propia tableta comienza a ralentizarse aún más. En pocas palabras: una tableta común para escribir en una pizarra no es adecuada. El máximo de sus capacidades consiste en mover el dedo por la pantalla con figuras bastante grandes. Pero es demasiado tarde para ofrecer esto a los escolares.



Bien, esta es una investigación puramente teórica, ¿verdad? Luego tomamos un digitalizador (también conocido como tableta gráfica) en formato Wacom Bamboo A8, y observamos al niño.



Tenga en cuenta que mi sujeto de prueba de seis años recibió una computadora portátil con un lápiz gráfico por primera vez en su vida. Nos tomó diez minutos adquirir las habilidades básicas para usar el bolígrafo, y ya en la segunda lección el niño usó la tableta con bastante confianza, borró de forma independiente del tablero, dibujó caras, flores, nuestro perro e incluso comenzó a presionar botones disponibles en épsilon, haciendo simultáneamente preguntas como "¿Por qué levantan la mano en la escuela?" Pero el resultado invariablemente dejaba mucho que desear. El hecho es que los diseñadores y artistas maximizan un fragmento de la imagen para dibujar un elemento, lo que hace que las líneas sean precisas. Aquí deberíamos ver el tablero completo, en una escala de 1: 1. Aquí y el adulto no caerá en la línea. Esto es lo que tenemos:



imagen



Veredicto final: ninguna escritura a mano está fuera de discusión. Y si queremos "poner una mano" a nuestros hijos, tenemos que lograrlo por nuestra cuenta, en el papel, la escuela no ayudará en esto.



Debo decir que el niño tomó todos mis experimentos con entusiasmo y, además, desde entonces me ha estado siguiendo con el rabo y pidiéndome que "encienda las recetas". Ya bien, la habilidad adquirida le será útil, solo para propósitos completamente diferentes.



De todos modos, como resultado de los experimentos, en realidad obtuve un MVP: un producto mínimo viable, casiAdecuado para lecciones en línea, con video / audio conferencia, pantalla compartida, pizarra interactiva, chat de texto simple y botón para levantar la mano. Esto es en caso de que el niño de repente no tenga micrófono. Sí, esto sucede, especialmente entre los niños que no han aprendido sus lecciones.



Pero en este barril de miel, lamentablemente, hay un par de cucharas de alquitrán.



Prueba de WebRTC



Cuchara número 1. Dado que nuestra comunicación por video utiliza conexiones directas entre clientes, el primer paso es probar la escalabilidad de dicha solución. Para la prueba, tomé una computadora portátil vieja con un i5-3230M de doble núcleo a bordo y comencé a conectarle clientes con cámaras web desactivadas, es decir, emulando un modo uno a muchos:



imagen



como puede ver, la computadora portátil experimental puede transmitir más o menos cómodamente a cinco clientes ( con carga de CPU dentro del 60%). Y esto siempre que la resolución del flujo de video saliente se reduzca a 720p (640x480px) y la velocidad de fotogramas a 15 fps. En principio, no está tan mal, pero al conectar una clase de varias docenas de estudiantes, tendrá que abandonar la "malla completa" a favor de la cascada, es decir, cada uno de los primeros cinco clientes transmite el flujo a los siguientes cinco, y así sucesivamente.



Cuchara número 2.Para crear una conexión interactiva directa (ICE) entre los clientes, deben evitar los firewalls y NAT. Para hacer esto, WebRTC utiliza un servidor STUN , que informa a los clientes de los parámetros de conexión externos. Se cree que en la mayoría de los casos esto es suficiente. Pero tuve casi inmediatamente "suerte":



Como puede ver, el depurador se queja de la imposibilidad de la conexión ICE y requiere que se conecte un servidor TURN, es decir, un relé. Y esto ya es CARO. Un servidor de señalización simple es indispensable. Conclusión: debe pasar todas las transmisiones a través del servidor de medios.



Servidor multimedia



Para las pruebas, usé aiortc . Un desarrollo interesante, le permite conectar el navegador directamente al servidor a través de WebRTC. No se requiere señalización separada, se puede utilizar el canal de datos de la propia conexión. Esto funciona, todos mis puntos de prueba conectados sin problema. Pero el problema con el rendimiento. Un simple eco de una transmisión de video / audio con los mismos límites de 720p y 15 fps se comió el 50% de mi CPU virtual en el VDS de prueba. Además, si la carga aumenta al 100%, la transmisión de video no tiene tiempo para descargarse a los clientes y comienza a obstruir la memoria, lo que finalmente conduce a la detención del servidor. Obviamente, el Python que nos encanta usar para el procesamiento de E / S no está muy ligado a la CPU. Tendremos que buscar una solución más especializada, por ejemplo, Januso Jitsy .



En cualquier caso, una solución completa requerirá servidores dedicados, según mis estimaciones, a razón de 1 núcleo por sala (clase). Esto ya cuesta dinero y va más allá de las simples pruebas, por lo que en este punto consideraré la primera fase de mi investigación completa.



conclusiones



1. Por decirlo suavemente, es extraño ver instrucciones de descarga y enlaces para registrarse en el programa del antiguo enemigo potencial número 1 (aquí sobre Microsoft Teams) en el portal oficial de la Federación Rusa . Y esto está en la era de las sanciones y la sustitución de importaciones.

No, personalmente estoy a favor de la amistad de los pueblos y, en general, de todo tipo de tolerancia, pero ¿de verdad soy solo yo de tal "integración" que se me erizan los pelos de punta? ¿No son nuestros desarrollos?



2. Integración de MES / NES con escuelas. En realidad, los desarrolladores de MES son geniales, incluso se integraron con Yandex.tutor. ¿Qué pasa con la calificación en tiempo real durante las lecciones, cuándo habrá una API? ¿O no soy consciente de algo?



3. Al elegir una forma de educación a distancia o familiar, debe ser honesto consigo mismo: no podrá trasladar la responsabilidad de la educación de su hijo a la escuela. Todo el trabajo de impartir lecciones (en el caso de la educación familiar), mantener la disciplina y la autoorganización (en cualquier caso) recae íntegramente en los padres. Debes ser consciente de esto y encontrar tiempo para las clases. Sin embargo, en familias numerosas esto no debería ser un problema.



4. No proporcionaré enlaces a escuelas en línea seleccionadas aquí, para que no se considere un anuncio. Solo diré que hemos elegido colegios privados de precio medio. En cualquier caso, el resultado final dependerá del niño y no lo recibiremos antes de septiembre.



¿O tiene sentido llevar el desarrollo iniciado aquí a su conclusión lógica y organizar su propia escuela? ¿Qué piensas? ¿Hay personas de ideas afines con conocimientos especializados y experiencia en el campo de la educación?



Enlaces útiles:

Escuela electrónica rusa Escuela

electrónica de Moscú

Biblioteca MES para

desarrolladores



All Articles