Tutorial: Introducción a la POO mediante ejemplo practico 

Python Screensaver. Debido a que el proceso de actualizar pantalla depende de la velocidad del procesador, en equipos modernos se ve feo.

Escritura de un programa simple de salvapantallas con Python, utilizando programación orientada a objetos.

Tabla de Contenidos:

  • Introducción
  • Sobre el aprendizaje desde los fundamentos
  • Estructura del Tutorial
  • Requerimientos previos del tutorial
  • Desarrollo
    • Objeto «universo.cielo.componentes»
    • Objeto «universo.estrellas.componentes»
    • Raíz del programa: «main.py»
  • Flujo del programa
    • Inicialización de clases
  • Conclusiones
  • Webgrafía

Introducción

Como buen principiante que soy, he tenido que deambular buscando tutoriales que me expliquen como hacer ciertas cosas y que resuelven con relativo éxito mis necesidades. Además, mis comienzos en la programación con la universidad fueron muy anticuados, siendo que estudie programación estructurada en lugar de programación orientada a objetos, lo que me dificulta mucho aprender lo ultimo.

Como seguro se habrán dado cuenta, la mayoría de los tutoriales buscan llegar de un punto «A» a un punto «B» sin complicarse mas, para lo que recurren a una forma simple de programación que puede derivar en lo que se conoce como «código spaguetti», mientras que para aprender programación orientada a objetos, hay que recurrir a textos mecánicos que explican superficialmente y usan ejemplos muy cortos que no aportan demasiado para entender los conceptos.

Este tutorial probablemente no sea el mejor de su tipo, pero mi objetivo es escribir algo que siento que me hubiese servido a entender mejor algunos conceptos, para lo cual he recurrido a un programa escrito en Python por Suraj Singh quien ha escrito un tutorial breve, pero ha aportado un código super comentado que he traducido, comentado mas y reestructurado para poder explicar algunos conceptos que considero son necesarios para entender mejor sobre Python y programación orientada a objetos.

Quiero aclarar que el tutorial de Suraj Singh no es malo, de hecho, lo considero genial y es la razón por la que lo estoy usando de ejemplo, sino que quiero expandir lo que enseña para cumplir con mis expectativas sobre el aprendizaje desde los fundamentos.

Sobre el aprendizaje desde los fundamentos

Cuando comenzamos a programar, es muy probable que hayamos empezado por necesidad, por lo cual seguramente habremos buscado con desesperación códigos y los hemos apilado hasta que funcionaran. Esto nos permite aprender muchas cosas y muy rápido, con lo que podemos concluir con éxito lo que nos movió a buscar aprender a programar en primer lugar, pero nos hace confiarnos y descuidar los fundamentos que mas adelante nos permitirían resolver tareas con mas eficiencia y eficacia.

Estructura del Tutorial

Este tutorial tratará de explicar de forma detallada todos los conceptos requeridos para lograr la creación de un salvapantalla simple escrito en Python. Hay que recordar que no es un salvapantalla real, en el sentido de que no puede ser instalado en las plataformas nativas de salvapantallas de Windows o Linux y solo es un modelo básico para explicar los conceptos.

Estará agrupado en dos partes:

  • Tutorial documentado con código y gráficos.
  • Código comentado, listo para ejecutar.

Requerimientos previos del tutorial

  • Saber escribir un hola mundo en Python

Si, solo eso. Hay muchas cosas implicadas en escribir hola mundo en Python, por ejemplo, tener instalado el compilador, saber usar la consola y muchas cosas mas.

Al tener instalado el compilador también se incluyen muchas herramientas y en la mayoría de instalaciones al menos, ya vienen incorporadas las herramientas de Tkinter, para la creación de ventanas en modo gráfico.

Aunque el tutorial puede ser ejecutado en el «Notepad» si les parece bien, yo recomiendo el uso de «Visual Studio Code», junto a los complementos de Python, porque tiene funciones de asistencia que facilita depurar e incluso aprender como funcionan las cosas. Si quieres, puedes presionar la tecla Control + Click izquierdo para poder ver la función, clase o variable donde fueron definidas.

Desarrollo

La programación orientada a objetos, busca la optimización del código mediante el agrupamiento de funciones y atributos conocidos como «objetos». Para entenderlo provisionalmente, he decidido tratar de visualizar, tanto la estructura de carpetas como la organización de funciones, clases y atributos, como objetos del mundo real, por lo que la estructura del directorio representa una caja y la he etiquetado como «python screensaver»

Dentro de esta caja he creado el primer archivo llamado «main.py» que contendrá la lógica del programa y dentro de la misma, he agregado otra caja que he llamado «universo».

Python permite trabajar con módulos, que no son mas que archivos que contienen código adicional que mantenemos separado del archivo principal para que sea mas fácil de editar y depurar. Durante la ejecución, el interprete unirá todos los archivos en un solo script y los leerá secuencialmente para poder ejecutar el programa en un proceso detallado en el modelo de ejecución, en la documentación oficial.

Para crear un módulo, debemos crear una carpeta que contenga un archivo vacío con nombre «__init__.py». una vez creado, no le volveremos a tocar.

Dado que el programa que estamos escribiendo es un salvapantalla de un cielo estrellado, los nombres de los módulos deberían reflejar esto, por lo que la carpeta universo contendrá al cielo, con sus funciones(o sea, el código que define su comportamiento) y atributos y a las estrellas, también con sus funciones y atributos. (Podríamos ignorar un poco el debate de si el cielo contiene a las estrellas o el universo contiene al cielo). Esto deja nuestro directorio de la siguiente manera:

Jerarquía archivos Python screensaver

Es recomendable que cada parte del código sea fácil de entender, que cada nombre y variable sea fácil de deducir y todo este muy bien ordenado porque, aunque al inicio funcione, si hay que modificar o corregir mas adelante, puede llegar a ser muy complicado y enigmático. Es por eso que en esta ocasión he elegido este esquema para los nombres de carpetas y módulos. Por supuesto, si se desea se puede usar cualquier esquema, no hay reglas estrictas en este sentido.

Nota: Usualmente el primer archivo de tu código debería llamarse «main.py», puesto que este es el nombre mas frecuentemente usado para indicar que todo el programa comienza por allí, pero en otros lenguajes de programación, como JavaScript(con NodeJS), se llaman «index.js». Esto no es estrictamente necesario puesto que el primer archivo puede tomar cualquier nombre. Solo es un acuerdo tácito entre programadores para facilitar la lectura del código.

En el archivo «main.py» vamos a comenzar haciendo nuestras primeras importaciones de módulos del sistema:

import tkinter as tk
import time
import random

Los módulos se pueden importar por su nombre, pero durante la escritura del código, tal vez prefieras utilizar un «alias» o apodo, para reducir la cantidad de texto al escribir. En mi caso he importado el módulo tkinter como tk, pero en otros tutoriales pueden optar por otros nombres. Por supuesto, también puedes importarlo sin ningún alias, no hay problema con eso y como ves, la mayor parte de los módulos aquí usados aparte de tkinter, se usan de esa manera.

Nuestro salvapantalla tiene que preparar algunas cosas antes de visualizarse, por lo que nuestro siguiente componente en ser cargado es la clase «cielo» dentro del módulo «universo».

Cuando creamos nuestra estructura de archivos, consideramos la carpeta «Python screensaver» como nuestro contenedor y dentro del mismo, creamos la carpeta «universo».

Al definir el archivo «__init__.py», la carpeta pasa a ser parte del módulo, por lo que al importar el objeto «cielo», la buscará en el módulo universo, según este esquema:

import nombre_módulo.nombre_objeto

Así que importaríamos nuestro módulo de la siguiente manera:

import universo.cielo

Nuestro trabajo ahora consiste en desarrollar el objeto «cielo.componentes».

En el módulo cielo debemos repetir la importación de los módulos, pero en este caso solo hace falta uno:

import tkinter as tk

Además, importaremos también el otro objeto creado en el módulo universo llamado estrellas.

import universo.estrellas

Objeto «universo.cielo.componentes»

Según la Wikipedia:

Un objeto es un ente orientado a objetos (programa de computadoras) que consta de un estado y de un comportamiento, que a su vez constan respectivamente de datos almacenados y de tareas realizables durante el tiempo de ejecución. Un objeto puede ser creado instanciando una clase, como ocurre en la programación orientada a objetos, o mediante escritura directa de código y la replicación de otros objetos, como ocurre en la programación basada en prototipos.

En nuestro caso, los atributos de nuestro módulo «cielo» se encuentran en nuestra clase «componentes» donde está definida una función que establece los parámetros iniciales de funcionamiento y variables que contienen sus atributos (def __init__(self, *args, **kwargs)) y dos funciones que definen el comportamiento del cielo (def crear_estrellas(self) y def actualizar_pantalla(self))

El código de la clase «universo.cielo.componentes» queda entonces de la siguiente manera:

import tkinter as tk
import universo.estrellas

CANTIDAD_ESTRELLAS = 2

class componentes(tk.Canvas):
    def __init__(self, *args, **kwargs):
        tk.Canvas.__init__(self, *args, **kwargs) 
        self.estrellas = []
        self.crear_estrellas()

def crear_estrellas(self):
    for i in range(CANTIDAD_ESTRELLAS):
        self.estrellas.append(universo.estrellas.componentes(self))
    return

def actualizar_pantalla(self):
    for i in self.estrellas:
        i.actualizar()
    return

Objeto «universo.estrellas.componentes»

Nota: No hay una forma lineal de escribir código en cuanto a programación orientada a objetos se refiere. Como te has dado cuenta, hasta el momento hemos creado una clase y ahora crearemos otra clase, ninguna de las cuales puede ser ejecutadas individualmente. Nuestro archivo «main.py» continua vacío excepto algunas importaciones que agregamos al principio. Esto es debido a que el código ya ha pasado por algunas pruebas y esquematizaciones, lo que permite crear este código ya organizado.

Es cuestión del programador el determinar la mejor forma de escribir su código, pero para practicar te recomiendo escribir programas sencillos en un solo archivo y practicar luego modularizándolos y convirtiendo en objetos algunas funciones y variables. Lo importante es entender los conceptos antes de tirarse a un proyecto mas grande, aunque si han llegado a este tutorial, es probable que ya hayan puesto en marcha algo de código antes de entender como funciona.

Al igual que en el módulo anterior, hacemos las respectivas importaciones, creamos las variables globales y creamos la clase:

import random

VELOCIDAD_RADIO=[i/10.0 for i in range(-10,-2)]+[i/10.0 for i in range(2,10)]
RADIO=25
COLOR_ESTRELLA = "yellow"

class componentes:
    def __init__(self, padre):
        self.padre = padre
        self.comenzar_movimiento()
        self.crear_circulo_pequeño()
    
    def comenzar_movimiento(self):
        self.x1 = self.padre.winfo_width()/2
        self.y1 = self.padre.winfo_height()/2
        self.velocity_x = random.choice(VELOCIDAD_RADIO)
        self.velocity_y = random.choice(VELOCIDAD_RADIO)
        return

    def crear_circulo_pequeño(self):
        x1=self.x1
        y1=self.y1
        x2,y2=x1+RADIO, y1+RADIO
        self.estrella = self.padre.create_oval(x1,y1,x2,y2, fill=COLOR_ESTRELLA)
        return

    def parar_movimiento(self):
        self.padre.coords(self.estrella, self.x1,self.y1,self.x1+RADIO,self.y1+RADIO)
        self.comenzar_movimiento()
        return

    def actualizar(self):
        self.padre.move(self.estrella, self.velocity_x, self.velocity_y)
        x,y = self.padre.coords(self.estrella)[:2]
        if x<0 or x>1500:
            self.parar_movimiento()
        elif y<0 or y>1000:
            self.parar_movimiento()
        return

Raíz del programa: «main.py»

Podría parecer un poco extraño no haber comenzado desde el inicio, pero el objetivo de este tutorial no es explicar el diseño de un código modular, sino explicar los conceptos implicados en el desarrollo del código mediante programación orientada a objetos. Debido a esto, recién llegamos a la parte que unificará todo el código escrito hasta el momento.

El código entonces, quedaría así:

import tkinter as tk
import time
import random
import universo.cielo

COLOR_FONDO="black"
NIVEL_TRANSPARENCIA=1
EVENTOS_DE_CIERRE = ['<Any-KeyPress>', '<Any-Button>']

def main():
    raiz=tk.Tk()#establecer la ventana raiz o inicial

    pantalla=universo.cielo.componentes(raiz,bg=COLOR_FONDO)
    pantalla.pack(expand="yes",fill="both") 
    
    raiz.wait_visibility(pantalla)
    raiz.wm_attributes('-alpha',NIVEL_TRANSPARENCIA)
    raiz.wm_attributes("-topmost", True)
    raiz.overrideredirect(1)
    raiz.state('zoomed')

    def salir(event):
        raiz.destroy()
        return
    
    for seq in EVENTOS_DE_CIERRE:
        raiz.bind_all(seq, salir)

    while True:
        raiz.update()
        raiz.update_idletasks()
        pantalla.actualizar_pantalla()

if __name__ == '__main__':
main()

Flujo del programa

El código ya se encuentra completamente comentado y explicando que hace cada función, variable, clase y modulo, pero hare unas cuantas aclaraciones aquí.

Inicialización de clases

Importar los módulos no es suficiente para empezar a usarlos. Es necesario inicializarlos, para lo cual, se los llama solos o dentro de una variable, pasándoles los parámetros necesarios para funcionar.

El modulo «universo.cielo.componentes» es inicializada en la función «main()» del archivo main.py de la siguiente manera:

pantalla=universo.cielo.componentes(raiz,bg=COLOR_FONDO)

La clase «universo.cielo.componentes» recibe como parámetro, la inicialización que hemos realizado del componente «tkinter». Esto nos evita tener que inicializar las clases y redefinir nuestros parámetros cada vez que lo necesitemos. Reutilizar código es una de las piezas clave de la Programación Orientada a Objetos y en este caso, lo que haremos se conoce como «Herencia»

La clase «universo.cielo.componentes» recibe los parámetros e inicializa las variables y funciones necesarias. Es decir, crea una instancia de la ventana que hemos creado, crea un arreglo que contendrá las estrellas e inicializara la función «crear_estrellas()» para definir el comportamiento inicial de la clase.

def __init__(self, *args, **kwargs):
    tk.Canvas.__init__(self, *args, **kwargs) 
    self.estrellas = []
    self.crear_estrellas()

En Python, se utiliza la palabra «self» para representar una instancia de la clase. Esto le permite acceder a los atributos y funciones que esta posee. Todas las funciones definidas en la clase, deberán utilizar esta palabra para poder acceder a sus variables inicializadas en «__init__»

El arreglo «self.estrellas» no contendrá datos, sino instancias de la siguiente clase que vamos a inicializar: «universo.estrellas.componentes»

La función «crear_estrellas()» crea un ciclo donde se agregan diferentes instancias de la clase «universo.estrellas.componentes» de acuerdo a la cantidad que hayamos definido en la variable «CANTIDAD_ESTRELLAS»

def crear_estrellas(self):
    for i in range(CANTIDAD_ESTRELLAS):
        self.estrellas.append(universo.estrellas.componentes(self))
    return

Las clases son instanciadas cada vez que son invocadas. En el caso de «universo.estrellas.componentes», sera instanciada 2 veces según lo definido en «CANTIDAD_ESTRELLAS», lo cual significa que el objeto estrella llega a existir dos veces, lo cual es equivalente a tener físicamente dos estrellas, con sus respectivos atributos y comportamiento.

La clase «universo.estrellas.componentes», al momento de ser inicializada, heredará todos los atributos de la clase que la llama, estableciéndose una relación «Padre-Hija» Esto se logra enviando como parámetro la palabra «self» y recibiéndola en una variable que en este caso llamaremos «self.padre»

def __init__(self, padre):
   self.padre = padre
   self.comenzar_movimiento()
   self.crear_circulo_pequeño()

La clase «universo.estrellas.componentes» al ser inicializada, guarda la información de la clase padre en la variable «self.padre» y llama dos funciones: «self.comenzar_movimiento()» y «self.crear_circulo_pequeño()». Estas funciones establecen los parámetros iniciales del objeto en la pantalla y crean el objeto en la pantalla.

Una vez inicializadas las clases, la función «main()» tiene acceso completo a sus atributos y funciones, por lo cual, se procede a actualizarlas desde el loop«While True»

while True:
    raiz.update() 
    raiz.update_idletasks()
    pantalla.actualizar_pantalla()

La variable «pantalla» ahora tiene acceso a la función «actualizar_pantalla()» dentro de la clase «universo.cielo.componentes» la cual hará un barrido de los objetos creados en «self.estrellas» para que actualicen sus propiedades.

«universo.cielo.componentes.actualizar_pantalla()»

def actualizar_pantalla(self):
    for i in self.estrellas:
        i.actualizar()
    return

«universo.estrellas.componentes.actualizar()»

def actualizar(self):
    self.padre.move(self.estrella, self.velocity_x, self.velocity_y)
    x,y = self.padre.coords(self.estrella)[:2]
    if x<0 or x>1500:
        self.parar_movimiento()
    elif y<0 or y>1000:
        self.parar_movimiento()
    return

Conclusiones

La programación orientada a objetos puede ser muy confusa a veces. Como hemos visto, un programa relativamente sencillo puede ser un laberinto difícil de rastrear, sin embargo, nos permite realizar cosas complejas de forma mas eficiente.

Entender la programación orientada a objetos desde su enfoque inicial, como una representación de los objetos de la vida real, puede facilitar mucho las cosas. Es de sentido común que todo objeto que nos rodea, tiene atributos y comportamientos definidos, pero es un poco mas abstracto los conceptos de herencia, abstracción, polimorfismo, acoplamiento y encapsulamiento, pero es un buen punto de partida.

Conforme se siga practicando, es posible entender cosas mas complejas y también es posible llegar a entender, por que se hizo tan popular en la actualidad.

Webgrafía

Módulos en Python. Por Learn Python. https://www.learnpython.org/es/Modules%20and%20Packages

how to create beautiful moving stars/snow like screen saver using python and tkinter – tutorial – two | python gui example. Por Suraj Singh. https://www.bitforestinfo.com/blog/04/11/how-to-create-beautiful-movingstars-r-snow-like-screensaver-using-python-and-tkinter.html

Objeto(programación) https://es.wikipedia.org/wiki/Objeto_(programación)

Código Espagueti https://es.wikipedia.org/wiki/Código_espagueti

Modelo de Ejecución https://docs.python.org/es/3/reference/executionmodel.html

Herencia (informática)https://es.wikipedia.org/wiki/Herencia_(inform%C3%A1tica)

Código y esquema de directorio:

Jerarquía archivos python screensaver
Jerarquía archivos python screensaver
# Importar los componentes necesarios
import tkinter as tk
import time
import random

#importar los componentes que contienen las clases que hemos creado
import universo.cielo

#Definir variables globales
COLOR_FONDO="black" #Establecer la variable que define el atributo del color del cielo
NIVEL_TRANSPARENCIA=1
EVENTOS_DE_CIERRE = ['<Any-KeyPress>', '<Any-Button>'] #Se puede crear un arreglo de eventos para inicializarlos todos de una vez


def main():
    raiz=tk.Tk()#Establecer la ventana raiz o inicial

    pantalla=universo.cielo.componentes(raiz,bg=COLOR_FONDO) #crear la ventana, utilizando la clase «cielo» como lienzo donde se dibujaran los componentes
    pantalla.pack(expand="yes",fill="both") #agrega los componentes en el lienzo
    
    raiz.wait_visibility(pantalla) #Se espera a que la ventana sea construida para cargar los componentes
    raiz.wm_attributes('-alpha',NIVEL_TRANSPARENCIA) #Se agrega el atributo de transparencia a la ventana
    raiz.wm_attributes("-topmost", True) #Se agrega el atributo «por encima de todo» a la ventana
    raiz.overrideredirect(1) #Evita redimensionar la ventana
    raiz.state('zoomed') #Inicia la ventana en estado "maximizado"

    #Esta funcion permite terminar el programa al ser disparada por los eventos definidos en la variable EVENTOS_DE_CIERRE
    def salir(event):
        raiz.destroy()
        return
    
    #Permite vincular la ventana a la lista de eventos definidos en la variable «EVENTOS_DE_CIERRE»
    for seq in EVENTOS_DE_CIERRE:
        raiz.bind_all(seq, salir)

    while True:
        raiz.update() #Actualiza la ventana
        raiz.update_idletasks() #Actualiza la ventana si no hay actividad
        pantalla.actualizar_pantalla() #Recarga la ventana mediante la clase cielo

#establecemos la funcion main como arranque al iniciar el script
if __name__ == '__main__':
 main()
#Importar los componentes necesarios
import tkinter as tk

#Importar los componentes que contienen las clases que hemos creado
import universo.estrellas

#Crear una variable globlal para la cantidad de estrellas en pantalla.
CANTIDAD_ESTRELLAS = 2

# Definir la clase componentes
class componentes(tk.Canvas):
    # Definir la clase inicial, que recibira los parametros desde donde sea inicializada.
    def __init__(self, *args, **kwargs):
        tk.Canvas.__init__(self, *args, **kwargs) # Crear un objeto tkinter con los parametros iniciales
        self.estrellas = [] # Establecer un arreglo de «estrellas» obtenidas de la clase «universo.estrellas.componentes»
        self.crear_estrellas() # Llamar a funcion crear_estrellas() al inicializar la clase

    # Funcion para crear estrellas
    def crear_estrellas(self):
        for i in range(CANTIDAD_ESTRELLAS): # Crea un arreglo de instancias de universo.estrellas.componentes de acuerdo a la cantidad definida en la variable CANTIDAD_ESTRELLAS
            self.estrellas.append(universo.estrellas.componentes(self))
        return

    #Funcion para actualizar la posicion de las estrellas en el cielo
    def actualizar_pantalla(self):
        for i in self.estrellas:
            i.actualizar()
        return

import random

VELOCIDAD_RADIO=[i/10.0 for i in range(-10,-2)]+[i/10.0 for i in range(2,10)]
RADIO=25
COLOR_ESTRELLA = "yellow"



class componentes:
    # Definir la clase inicial, que recibira los parametros desde donde sea inicializada.
    def __init__(self, padre):
       self.padre = padre
       self.comenzar_movimiento()
       self.crear_circulo_pequeño()
    
    # Establece los parametros iniciales para realizar los movimientos del objeto en la pantalla
    def comenzar_movimiento(self):
        self.x1 = self.padre.winfo_width()/2
        self.y1 = self.padre.winfo_height()/2
        self.velocity_x = random.choice(VELOCIDAD_RADIO)
        self.velocity_y = random.choice(VELOCIDAD_RADIO)
        return

    # Crea un circulo en la pantalla
    def crear_circulo_pequeño(self):
        x1=self.x1
        y1=self.y1
        x2,y2=x1+RADIO, y1+RADIO
        self.estrella = self.padre.create_oval(x1,y1,x2,y2, fill=COLOR_ESTRELLA)
        return

    # Detiene el movimiento del objeto en pantalla
    def parar_movimiento(self):
        self.padre.coords(self.estrella, self.x1,self.y1,self.x1+RADIO,self.y1+RADIO)
        self.comenzar_movimiento()
        return

    # Actualiza la posicion del objeto en pantalla
    def actualizar(self):
        self.padre.move(self.estrella, self.velocity_x, self.velocity_y)
        x,y = self.padre.coords(self.estrella)[:2]
        if x<0 or x>1500:
            self.parar_movimiento()
        elif y<0 or y>1000:
            self.parar_movimiento()
        return

Actualización:

¡Codigo en GitHub!

Sistema Super Simple de Blogging (3SB)

Super Simple Blogging System aka 3sb
Interfaz de Usuario para 3sb

Autor: Drk0027

Versión: 1.0

ChangeLog: Changelog

Proyecto Desplegado: 3sb.interlan.ec 3SB en GitHub Pages

Documentación: Documentación

GitHub: 3SB en GitHub

Tabla de contenidos

Motivación

La motivación de este proyecto, simplemente es crear una herramienta practica, con la cual pueda entrenar mas mis conocimientos de JavaScript y PWA, además de tener un sistema de blogging muy simple que dependa muy poco del servidor.

Características

Este blog permite toda la gama de CRUD (Crear, Actualizar, Borrar) para cada elemento de la base de datos (entradas.json), pero los cambios se almacenan de forma local en el LocalStorage, por lo que si se quieren hacer públicos los cambios, es necesario exportar el nuevo archivo entradas.json y reemplazar al anterior.

Las características iniciales de este proyecto son las siguientes

  • CRUD completo Es decir, crear, actualizar y eliminar registros.
  • Personalización Se pueden usar plantillas Bootstrap, pero también, al ser tan simple, siempre y cuando se mantengan las etiquetas originales con sus id, puede ser modificado completamente a gusto
  • PWA Al disponer de Service Workers y ser servido mediante https, los datos pueden permanecer en el cliente fuera de lineal, pudiendo ser consultados libremente.
  • Responsive Las características del cliente definen la calidad de la lectura al ajustarse a los parámetros necesarios.
  • Notificaciones push Para poder notificar al cliente si hay una versión mas reciente del archivo entradas.json o indicar que el archivo local ha sido modificado y debe guardarse para mostrar los cambios de forma global.(tal vez próxima versión)
  • Edición con guardado automático Al editar una entrada, los cambios se almacenan automáticamente en el LocalStorage

Sistema de Blogging tradicional

El objetivo de este proyecto no es reescribir la rueda, sino ofrecer una alternativa a los modelos tradicionales de forma didáctica, para que quienes deseen, puedan aprender directamente del código.

Para esto, se mantienen aspectos tradicionales de los blogs como:

  • Presentación de entradas en forma descendente Muestra las entradas mas recientes primero en la pantalla inicial
  • Búsqueda simple por contenido de las entradas La búsqueda es muy simple, se selecciona un array de palabras ingresadas en el campo de búsqueda y se confirma si estas existen en las entradas. Se creara una lista de entradas que contengan los criterios de búsqueda (por implementar)
  • Búsqueda por etiquetas Se mostraran solo las entradas que tengan las etiquetas seleccionadas
  • ¿Comentarios? Dado que solo se cargan paginas estáticas, no es posible almacenar comentarios. En todo caso, tal vez sea posible vincular las url con sistemas como Discus y otros.
  • Gestión SEO No es precisamente imposible, pero la mayor parte del procesamiento se realiza desde el lado del cliente, por lo que, si bien, los motores de búsqueda pueden encontrar por ejemplo, el archivo index.html, no podrían leer los contenidos de las entradas, ya que esto requiere JavaScript y no solo texto plano. He leído que hay algunos buscadores que ya crean una representación virtual del tiempo de ejecución, por lo que pueden localizar información que solo se carga durante la ejecución en el cliente, pero no he investigado mas.

Limites del sistema

Realmente no se que limites podría tener, pero estimo que esta bastante limitado, debido a que carga un archivo json que crecerá bastante con el tiempo.

Al parecer cada navegador tiene sus propias restricciones de memoria, pero básicamente la mayoría tienen espacio ilimitado para el LocalStorage, el problema esta mas relacionado con la memoria RAM.

Si bien el almacenamiento no es mayor problema, el archivo Json es cargado directamente en la memoria RAM, así que su consumo es directamente proporcional al tamaño neto del archivo.

Los procesos del sistema no consumen demasiada memoria, pero al renderizar tienen que cargar el archivo por completo para procesarlo, es posible que comiencen a sentirse los efectos pasado los 10MB, puesto que el consumo de memoria suele incrementarse de forma exponencial al tamaño del archivo. Es bastante impresionante lo que puede cargar una base de datos normal. Mis respetos.

Solo por pura diversión, intentare hacer pruebas en un dispositivo kaiOS, Después de todo, este proyecto comenzó pensando en un Alcatel 3078A

Sobre las imágenes y archivos

Se pueden utilizar las mismas etiquetas para incrustar imágenes en el documento, siempre y cuando se hayan subido primero las imágenes al directorio destinado imágenes o en su defecto, donde se desee almacenar los recursos requeridos

Instalación

Basta con copiar los archivos en la carpeta publica del servidor. Tal vez cuando lo suba a GitHub también se pueda usar el comando git clone para descargar el código y ejecutarlo

Webgrafía

How to Build a Lightweight Blog

Is It Possible to Build a Blog With HTML Only?

Deploy a JavaScript only Blog With CMS.JS

CMS.JS Proyecto muerto

Acceder a un terminal mediante SSH inverso

Una de las cosas mas útiles que he deseado tener disponible, es el acceso SSH a mi computadora persona, desde cualquier parte del mundo. He conocido herramientas como ngrok y localtunnel que sirven, mas o menos, para lo que necesito, pero quería intentar con una herramienta que no dependa de servidores externos y ajenos a mi poder, sea porque no quiero compartir mis datos con terceros, o porque simplemente quería aprender a hacerlo por mi mismo.

Luego de mucho investigar, he encontrado algo que existe desde hace mucho tiempo y que probablemente es conocido por todo el mundo, pero recién aprendí a hacerlo por mi cuenta; SSH permite hacer túneles inversos.

Esquemas de control remoto

Desde el periodo de confinación por el COVID-19, todo tipo de servicios se hicieron sumamente populares, como zoom para las conferencias, y AnyDesk para trabajo remoto.

Aunque es de conocimiento común, se puede controlar un equipo utilizando el modelo Cliente-Servidor, donde el cliente se conecta al servidor y el servidor responde a sus peticiones.

Modelo Cliente-Servidor
Modelo Cliente-Servidor

En este modelo, el equipo a controlar debe ser el servidor, es decir, debe mantenerse escuchando para poder responder las peticiones del cliente, es decir, el controlador.

Este modelo es sencillo si hay conexión directa entre el cliente y el servidor, por lo que se puede utilizar la siguiente combinación de programas para el acceso remoto:

  • SSH – SSHd Acceso mediante consola (y montones de trucos mas).
  • RDP/XRDP – Escritorio remoto de Windows/Remina(Linux) Acceso mediante escritorio remoto usando el protocolo RDP de Windows.
  • Vino-Vinagre Acceso mediante VNC que admite múltiples sistemas operativos.

El problema entonces es que todo esto funciona en una red local, pero no a través de internet.

La gran nube

La red de redes
La red de redes

Podríamos definir a internet como la red que une a muchas redes pequeñas mediante un mapa de rutas complejas trazado por algoritmos aun mas complejos de entender.

Inicialmente, los proveedores de internet buscan conectar un solo dispositivo a internet, pero en una casa, esto no es lo ideal. Hay muchos dispositivos y cada día parecen haber mas, por lo que una sola conexión no abastecería.

Para solucionar esto, se crea una pequeña red local mediante routers que guiaran todas las conexiones hacia el exterior.

Es probable que por seguridad el proveedor de internet haya limitado las conexiones del router a solo salientes, por lo que no hay forma de conectarse desde afuera.

También es probable que el mismo proveedor haya creado una infraestructura compleja de subredes anidadas que dificultan aun mas encontrar el camino hacia un equipo en especifico desde el exterior. Casi cualquier dispositivo que no esté en la red local, estará en la misma situación, por lo que la comunicación entre ambos es virtualmente imposible.

La Solución

Internet es una maraña de dispositivos conectados, pero si alejamos lo suficiente nuestra perspectiva, nos daremos cuenta de que en realidad, parece una gigantesca red local.

Esto significa que es posible conectarse a algún nodo que tenga conexión directa y publica a internet, utilizando lo que es llamado IP Pública

Algunos proveedores de internet ofrecen el servicio de IP publica a sus clientes, por lo que ajustando algunas configuraciones en el router, ya se puede acceder desde cualquier parte del mundo a nuestro equipo, pero esto es innecesariamente peligroso, por cuanto internet es un lugar plagado de atacantes buscando vulnerabilidades en cada IP que puedan encontrar.

Para resolver esto y evitarle a los clientes todo tipo de pesadillas, las empresas proveedoras de servicios de control remoto, crearon servidores de conexión inversa, que sigue un modelo de Cliente – Servidor – Cliente.

Modelo Cliente Servidor Cliente
Modelo Cliente Servidor Cliente

Para este ejercicio, vamos a imitar el modelo que utilizan las grandes empresas y crearemos un puente para alcanzar algún equipo de nuestra red local, desde cualquier parte de internet.

Desarrollo de la Solución

Aunque técnicamente se puede realizar con solo dos dispositivos, lo mas común es que se necesiten tres, así que enumeraremos los requisitos a continuación:

  • Dispositivo de destino: Se encuentra aislado en una red local. y es incapaz de recibir conexiones entrantes debido a la configuración de su red y su proveedor de internet, pero puede ejecutar conexiones salientes a cualquier parte de internet.
    • Servidor SSH Puede ser cualquiera, como OpenSSH o Dropbear. Nota importante: el dispositivo debe tener instalado algún servidor SSHd. He hecho pruebas con termux en Android y, aunque tiene uno, su problema esta relacionado con que no permite el login de usuario y contraseña, para lo cual se deben usar otros métodos de autenticación.
    • Cliente SSH Puede ser cualquiera.
  • Servidor: Se encuentra en un lugar accesible de internet y permite conexiones entrantes o salientes libremente. Generalmente es un servidor, pero puede ser cualquier dispositivo mientras tenga una IP pública.
  • Dispositivo de salida: Generalmente está en la misma situación que el dispositivo de destino, pero en otra red. lo importante es que tenga conexión saliente hacia el servidor.

Seguramente ya dispones de los dispositivos de destino y de salida, pero el servidor es algo menos probable, así que, por que no arriesgarse con un droplet de Digital Ocean?

DigitalOcean Referral Badge

Utilizando este enlace puedes crear una cuenta con un crédito inicial de 100 dólares, totalmente sin costo durante 60 días. Úsalo para aprender y practicar con servidores Linux y cuando se acabe el periodo de prueba, puedes comprar Droplets desde 5 dólares mensuales. Recuerda administrar responsablemente tus VPS para evitar sorpresas al final del mes.

Ahora vamos a empezar a aplicar la configuración de los distintos dispositivos implicados:

Dispositivo de Destino

Este dispositivo, que se encuentra atrapado en una red que no permite conexiones entrantes, va a permanecer conectado todo el tiempo al servidor para que el túnel se mantenga vivo. Eso significa que la ventana del terminal tendrá que mantenerse abierta en todo momento o de lo contrario el túnel morirá.

En las aplicaciones de control remoto esto también sucede, pero es mas transparente al usuario debido a que utilizan servicios para mantener vivo el cliente y revivir el túnel cada que sea necesario. En nuestro caso se puede realizar algo similar, enviando la conexión al fondo mediante screen u otras herramientas.

En el dispositivo de destino, hay que escribir lo siguiente

ssh -R 16789:localhost:22 usuario@servidor

donde:

  • ssh Inicia la aplicación de shell segura.
  • -R 16789:localhost:22 vincula un puerto arbitrario(de nuestra elección) en el dispositivo de destino, al puerto 22 del servidor SSHd.
    • 16789 es un puerto arbitrario, es decir, podemos poner cualquiera, siempre que sea un puerto libre en nuestro equipo.
    • localhost o 127.0.0.1 es la dirección local de nuestro dispositivo.
    • 22 es el numero del puerto al que vamos a redirigir la conexión entrante. En este caso, es el puerto de SSHd.
  • usuario@servidor
    • usuario Es el nombre del usuario existente en el servidor al que nos vamos a conectar y utilizar de puente.
    • servidor Es el nombre de dominio o dirección IP del servidor al que nos estamos conectando.

Al ejecutar este comando, se abrirá una conexión con el servidor, que deberás mantener viva tanto como quieras acceder por el túnel inverso. Por supuesto, mientras estas en ese túnel, puedes usar libremente al servidor como si fuera una conexión normal.

Dispositivo de salida

Puede parecer raro comenzar por el dispositivo de salida, pero asumiremos que en el servidor ya está SSHd bien configurado.

En el dispositivo de salida deberás acceder al servidor con una conexión normal de SSH

ssh usuario@servidor

Donde:

  • ssh Es el comando de la consola segura
  • usuario@servidor
    • usuario Es el usuario del servidor
    • servidor Es el nombre de dominio o la dirección IP del servidor

Una vez que haya una sesión iniciada en el servidor, debemos ejecutar este comando y tendremos acceso a la consola del dispositivo de destino.

ssh usuario@localhost -p 16789

Donde:

  • ssh Es el comando de la consola segura.
  • usuario@localhost Es el usuario del dispositivo de destino.
    • usuario es el usuario del dispositivo de destino.
  • -p 16789 Es el puerto que abrimos en el dispositivo de destino.

Si queremos acceder a nuestro dispositivo de destino, hemos de dejar encendido el túnel, pero una falla de la red podría causar que la conexión se cierre. Al no estar cerca del equipo para restaurar la conexión, no podremos revivir el túnel hasta que volvamos presencialmente a restaurarla, así que lo recomendable es utilizar una herramienta que reviva el túnel cada que sea necesario o utilizar un cliente que pueda mantener conexiones persistentes a pesar de los quiebres de red.

Mientras tanto, se pueden utilizar sin problema los demás protocolos que funcionan sobre SSH, como SFTP o SCP por ejemplo.

Eso si, hay que recordar que para conectarse, hay que usar el puerto arbitrario que hemos definido anteriormente.

SFTP:

 sftp -oPort=16789 usuario@localhost

SCP

scp -p -P 16789 /tmp/ssh.txt usuario@localhost:

Hay muchas dudas que me quedan pendientes pero las iré estudiando conforme vaya necesitando mas cosas, pero tener este túnel es algo interesante para practicar.

Bibliografía/Webgrafía

Conexión a servidor mediante túnel inverso ssh Obtenido de https://openwebinars.net/blog/conexion-servidor-mediante-tunel-inverso-ssh/

Túnel inverso. Obtenido de :https://mundo-hackers.weebly.com/tuacutenel-inverso-ssh.html

Creación de túnel inverso. Obtenido de https://campusvirtual.ull.es/ocw/pluginfile.php/2172/mod_resource/content/0/perlexamples/node48.html

Bitácora de desarrollo: Bot Descargar Musica 31/5/22

El articulo a continuación es una bitácora del desarrollo de un bot o plataforma de bots que actualmente tengo funcionando. Se puede acceder usando el siguiente URL: Descargar_Musica

He comenzado esta especie de portafolio con un pequeño bot de Telegram para descargar música desde YouTube. Para esto, he recurrido a las siguientes tecnologías:

Los cambios que he realizado recientemente son muy escasos. Mi conocimiento sobre javascript es relativamente superficial, por lo que he decidido aprender clases, módulos y librerías en javascript. Debido a esto, ahora se han realizado los siguientes cambios:

Creación del directorio data/descargar_musica/descargas

Con el fin de crear un sistema de control para bots unificado, he creado este directorio para que en el futuro, pueda agregar mas directorios de datos para cada bot. En esta situación en especifico, este cambio se hace por la necesidad de asegurar que si hay datos corruptos o incompletos, o un sistema de borrado deficiente, al modificar o borrar los archivos, esto no afecte a otros bots o al propio código fuente.

Creación de las librerias db_logging_system y telegram_logging_system

Con el fin de crear un código mas simple y fácil de mantener, he creado estas librerías universales, que permiten registrar a los nuevos usuarios que utilicen el bot y otra que registre los eventos en un canal distinto.

PD: Sigo sin entender mucho esto, pero ahi logre hacerlo funcionar.

Creación de un vídeo tutorial

Este bot no tiene mayor complicación. Lo único que requiere es que envíen un enlace y comienza a trabajar, sin embargo, siguen mandandole enlaces de spotify e incluso itunes. Si bajar música de estas plataformas fuera legal (y hubiese alguna ganancia aparte de practicar programación) seguro que implementaría una función adecuada, pero por el momento, esto no es asi.

Telegram tiene muchas funciones interesantes y una de mis preferidas son los bots inline. Es posible conseguir la url de un vídeo de youtube, usando el bot @vid y una vez seleccionado el vídeo, mi bot comenzará a trabajar.

Uso de los directorios data/descargar_musica/descargas y data/descargar_musica/info

En la revisión anterior, los vídeos se descargaban en el mismo directorio de ejecución. Sean estos archivos temporales o archivos completos, todos se guardaban sin reparo allí, por lo que si ocurría un error o el sistema caía abruptamente, este directorio que contiene los ejecutables, se llenaba de basura que era difícil de limpiar. Para esto, se ha creado el directorio descargas, dentro de uno asignado al bot en cuestión, donde irán todas las descargas realizadas y las que hayan fallado también.

el directorio info, existe en cambio para guardar datos permanentes que no son parte del código, como el vídeo instructivo mencionado previamente

Otros cambios

  • Se agregaron mas registros para mas eventos (errores, eventos, arranque y comandos)
  • Se limpió el código aplicando las nuevas librerías creadas
  • Se optimizo el tiempo de respuesta del bot
  • Se agregaron nuevos mensajes de respuesta
  • Se agregaron nuevas instrucciones para situaciones no controladas
    • En caso de introducir un enlace no reconocido, se agregó la misma instrucción del comando sobre_mi.
    • En caso de haber un error al descargar, se agregó un mensaje de notificación.
    • En caso de haber un error al convertir el archivo.
    • En caso de haber un error al eliminar los archivos temporales.

Proximos cambios

El bot ya se encuentra en un estado bastante aceptable para lo que queria aprender, pero deseo aprender mas cosas, por lo que espero poder agregar lo siguiente:

  • Registro particular para cada bot (sistema “Panoptic”)
  • Control de depuración por comandos para el bot
  • Control web para el bot
  • Control general para todos los bots en el sistema
  • Mejor registro de información común
  • Mejor registro de información especifica
  • Control personalizado de acuerdo al usuario
  • Integración con otras plataformas de bot (discord, twitch, etc)

Actualización del estado del bot de descargar música

No he tenido tiempo para hacer nuevas modificaciones al bot, pero por curiosidad revise las pocas estadísticas que he logrado recopilar. Son justamente estas estadísticas las que me disuaden de hacer mejoras, puesto que implican mucho esfuerzo adicional.

  • Cantidad de usuarios únicos: 580
  • Cantidad de nuevos usuarios por mes: 145
  • Cantidad de respuestas exitosas : 675
  • Cantidad de consultas de información: 45
  • Personas a las que invite personalmente: 3
  • Cantidad de URL correctas: 843
  • Cantidad de solicitudes malformadas: 348
  • Mensajes de spam: 2
  • Usuarios que escribieron mas de un mensaje: 320
  • Usuarios intensivos (mas de 10 mensajes): 30
  • Usuario que mas usa el bot: yo <3

Entre estos datos hay algunas cosas interesantes y desconcertantes. Por ejemplo, siendo que solo he invitado a tres personas, ¿De donde salieron esos 580 usuarios en apenas tres meses?

También es interesante notar que, a pesar de las instrucciones, hay cerca de 348 mensajes que no cumplen los requisitos para funcionar el bot. Muchos de usuarios, luego de un solo intento fallido, no vuelven a usar el bot nunca mas.

Además, se supone que un bot es una maquina que no es atendida a menos que requiera de cierto mantenimiento, dos de esos mensajes fueron intentos de estafa, donde me pedían depósitos a cierto numero de cuenta bancaria.

Después de ver estas cosas, he notado que hacen falta las siguientes cosas, que podrían ser mejoras inmediatas para el bot:

  • Mensajes variables: Actualmente los mensajes del bot están Hard Coded, por lo que para actualizar los mensajes, es necesario modificar el código del bot, para lo cual es necesario detenerlo y reiniciarlo, causando que usuarios poco pacientes pierdan el interés en el.
  • Sistemas de Login: Aunque he creado un sistema que hace que el bot me reporte mediante mensajes en un canal de Telegram, no se guarda nada en la base de datos, razón por la que no puedo obtener unas estadísticas confiables de los eventos que implican a mi bot.
  • Panel de control: Realmente no se si los otros bots lo tengan, todos los que he usado dependen únicamente de la interfaz de chat de Telegram, pero una interfaz web para el administrador suena genial, podría intentarlo.

El Voltaje de salida del modulo regulador cn2596-2 es igual al de entrada

cn2596-2
cn2596-2

He tenido este pequeñín durante algunos años dando vueltas por ahí, sin uso, debido a que al conectarlo, el voltaje de salida es igual al voltaje de entrada, por lo que siempre pensé que su uso no seria diferente de un cable, aun así, lo he conservado y he dedicado algo de tiempo a investigar su funcionamiento, hasta que finalmente encontré la respuesta en un video ruso que obviamente no logro entender, pero me hizo dar cuenta del problema.

Usando un multímetro midiendo los pines de salida, hay que girar el potenciómetro en sentido contrario a las manecillas del reloj hasta que el voltaje medido comience a cambiar.

Esto causara que el voltaje de salida medido por el aparato también cambie y se desajuste, por lo que por ahora, no confiaría en ese resultado, debido a que no es el dado por las pruebas.

Pongo las especificaciones técnicas y unas cuantas fotos de una tienda random donde lo encontré:

  1. Voltaje de entrada: 4-40 v (40 v máximo voltaje)
  2. Voltaje de salida: 1.25V – 37V (En el modo de descomprensión, la entrada debe ser superior a los dos voltios)
  3. Corriente de salida: 2 A salida continua.
  4. Potencia de salida: Máximo 15 w.
  5. Frecuencia: 150 KHZ.
  6. Pico de onda: 100 mv.
  7. Precisión del voltímetro: ± 5 ‰
  8. Potencia estática: 20 ma (desviación por voltaje de entrada, salida y display)
  9. Peso: 33g.10.
  10. Tamaño: 7cm*4cm.
  11. Con la función de display de voltaje, la precisión es de ± 0.05V, y el rango es de 0 – 40V.( Nota: para asegurar la máxima precisión, el voltaje de entrada debe ser superior a 4V)
  12. Botón para cambiar la medida del voltaje de entrada o salida y un led que indica el actual modo de medición, las configuraciones no se pierden al desconectar la alimentación
  13. Voltaje continuo ajustable de salida en un rango de 1.25V -37V, 4-40V de entrada. El voltaje de entrada debe ser superior a 1.5V)
  14. Oscilador de alta eficiencia ajustado a 150 kHz f
  15. Garantizado 3A de carga de corriente de salida.
  16. Protección por temperatura y limite de corriente.

Bibliografia/Webgrafia

Datasheet del modulo lm2596-d:

El video ruso que me sirvió:

Próximo material de estudio:

https://www.instructables.com/The-Introduction-of-LM2596-Step-Down-Power-Module-/

Notas adicionales

Yo compre este aparato en Banggood hace algunos años, pero no estoy seguro de si aun lo venden. Como mi pais tiene problemas de comercio exterior, banggood ya no manda casi nada aquí, y encima me bloquea los productos que sabe no me puede enviar.

Me interesa también la idea de hackearlo, pero por ahora me conformare con que pueda regular los voltajes de salida de forma correcta.

https://hackaday.io/project/19647-low-cost-programmable-power-supply/details

Primeras Pruebas con Solidoodle Press

La impresión 3D es una tecnología relativamente reciente, pero que ha alcanzado el mercado domestico gracias a iniciativas como el proyecto RepRap y Fab@Home sin embargo aun es muy experimental.

He conseguido una impresora básica, de marca Solidoodle y he comenzado a hacer algunos experimentos. Dada mi falta de experiencia los resultados son malos pero la meta es hacerla rendir.

Aquí algunos ejemplos de fallos.

El principal problema con esta impresora es la obstrucción del extrusor.

A pesar de las terribles reseñas que tiene esta impresora, en realidad no he tenido problemas tan serios o he sabido solventarlos. Debido a esto creo que publicare cada cierto tiempo como resolver los problemas que se presenten para que los visitantes puedan ver que hacer en caso de emergencia.

Los componentes de esta impresora no difieren mucho de los de otras, las piezas son intercambiables y a pesar de la caja que la contiene, es fácil llegar al extrusor en caso de problemas. Diria que su mayor defecto es el programa que viene por defecto que generalmente se puede arreglar cerrándolo y volviéndolo a abrir.

Es interesante que a pesar de los defectos que tiene, es bastante precisa con las piezas, pero de nuevo, el mayor problema que tiene es su software. Aquí unos ejemplos de piezas en mejor estado, ya solventado el problema del extrusor.

Aunque claro, todo requiere de prueba y error para funcionar.

Por ahora seguiré probando y en otros post mostrare progresos.

Interlan
A %d blogueros les gusta esto: