Translate

viernes, 6 de febrero de 2026

Seguridad y Mantenimiento de la Base de Datos

La seguridad es uno de los pilares más importantes de la administración de bases de datos.
El objetivo es proteger los datos contra accesos no autorizados, evitar ataques y preservar la integridad de la información.

Autenticación y Autorización (Roles y Permisos)

Autenticación

Proceso de verificar la identidad del usuario que intenta acceder a la base de datos.

Ejemplo:
En PostgreSQL:

CREATE USER app_user WITH PASSWORD 'segura123';

En MySQL:

CREATE USER app_user'@'localhost' IDENTIFIED BY 'segura123';

Buenas prácticas:

  • Usar contraseñas robustas.

  • Implementar autenticación integrada (LDAP, Active Directory, IAM).

  • Evitar el uso de cuentas compartidas.


Autorización

Define qué puede hacer cada usuario dentro del sistema, mediante roles y permisos.

Ejemplo:

CREATE ROLE ventas;

GRANT SELECT, INSERT ON pedidos TO ventas;

GRANT ventas TO app_user;

Principio del mínimo privilegio:
Otorgar solo los permisos estrictamente necesarios.



Tipos de permisos más comunes

Tipo

Descripción

Ejemplo

SELECT

Leer datos

GRANT SELECT ON clientes TO usuario;

INSERT

Agregar registros

GRANT INSERT ON pedidos TO usuario;

UPDATE

Modificar registros

GRANT UPDATE ON productos TO usuario;

DELETE

Eliminar registros

GRANT DELETE ON pedidos TO usuario;

EXECUTE

Ejecutar funciones o procedimientos

GRANT EXECUTE ON sp_backup TO admin;


Inyección SQL: Prevención

La inyección SQL (SQL Injection) es uno de los ataques más frecuentes en aplicaciones conectadas a bases de datos.
Consiste en insertar código SQL malicioso en campos de entrada para manipular consultas.

Ejemplo de ataque:

-- Entrada del usuario

' OR 1=1 --


-- Consulta final vulnerable

SELECT * FROM usuarios WHERE nombre = '' OR 1=1 --' AND clave = '';

Esto devuelve todos los usuarios.


Prevención

  1. Usar consultas parametrizadas o prepared statements.


# Ejemplo en Python con psycopg2

cur.execute("SELECT * FROM usuarios WHERE nombre = %s AND clave = %s", (user, password))

  1. Validar y sanitizar entradas.

    • Aceptar solo tipos esperados (números, fechas, correos, etc.).

    • Rechazar caracteres especiales o comillas innecesarias.

  2. Evitar concatenar strings en consultas.

  3. Restringir permisos del usuario de conexión.

    • Que el usuario de la aplicación tenga solo permisos de lectura/escritura, no de administración.


Cifrado de Datos Sensibles

El cifrado protege la confidencialidad de la información, incluso si el almacenamiento físico es comprometido.

1. Cifrado en Reposo

Protege los datos almacenados en disco (archivos, backups, logs).

Ejemplo:

  • PostgreSQL + pgcrypto:


SELECT pgp_sym_encrypt('123456789', 'claveSecreta');

SELECT pgp_sym_decrypt(columna_cifrada, 'claveSecreta');

  • MySQL:


SELECT AES_ENCRYPT('123456789', 'clave');

SELECT AES_DECRYPT(campo, 'clave');

2. Cifrado en Tránsito

  • Usar SSL/TLS para proteger las conexiones.

  • Configurar los clientes con sslmode=require.

3. Cifrado a nivel de columna o campo

Ideal para datos como contraseñas, documentos, tarjetas o DNIs.

Consejo:

Usar funciones de hash (como bcrypt o SHA-256) para contraseñas, no cifrado reversible.

jueves, 5 de febrero de 2026

Herencia Múltiple en Python


En la mayoría de los lenguajes orientados a objetos, una clase hereda de una sola clase base.

Sin embargo, Python permite herencia múltiple, lo que significa que una clase puede derivar de más de una clase padre.


class A:

    def metodo(self):

        print("Método de A")


class B:

    def metodo(self):

        print("Método de B")


class C(A, B):  # Hereda de A y B

    pass


obj = C()

obj.metodo()


Salida : Método de A


Python resuelve los métodos según el orden de resolución de métodos (MRO, Method Resolution Order).

En este caso, busca primero en A, luego en B, y finalmente en object.


Podemos ver el orden con:

print(C.__mro__)


Que muestra algo como:

(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)



Python permite usar super() para encadenar llamadas a métodos en la jerarquía.


class A:

    def metodo(self):

        print("A.metodo()")

        super().metodo()


class B:

    def metodo(self):

        print("B.metodo()")

        super().metodo()


class C:

    def metodo(self):

        print("C.metodo()")


class D(A, B, C):

    pass


d = D()

d.metodo()


Salida: 

A.metodo()

B.metodo()

C.metodo()


El orden sigue el MRO: D → A → B → C → object.


Una forma muy común de usar herencia múltiple en Python es con mixins: clases pequeñas que agregan comportamiento extra sin ser jerarquías principales.


class LoggerMixin:

    def log(self, mensaje):

        print(f"[LOG]: {mensaje}")


class Worker:

    def trabajar(self):

        print("Trabajando...")


class Empleado(LoggerMixin, Worker):

    def realizar_tarea(self):

        self.log("Iniciando tarea")

        self.trabajar()


e = Empleado()

e.realizar_tarea()


Salida:

[LOG]: Iniciando tarea

Trabajando...


Los mixins son una forma elegante de reutilizar código sin acoplar las clases principales.


Resumiendo:

  • Python permite herencia múltiple.
  • El MRO define el orden en que se buscan los métodos.
  • super() respeta ese orden automáticamente.
  • Los mixins son el uso más común y limpio de la herencia múltiple.



martes, 3 de febrero de 2026

Monitoreo Básico de Recursos de base de datos

 El monitoreo permite detectar cuellos de botella en el sistema físico (CPU, memoria, disco, red).

Una base de datos optimizada a nivel lógico puede seguir rindiendo mal si el servidor está saturado.


🔸 1. CPU

La CPU se usa para procesar consultas, ordenar datos, aplicar filtros, etc.
El síntoma típico de CPU saturada es un aumento de los tiempos de respuesta, especialmente en consultas complejas o con muchas agregaciones.

📘 Herramientas comunes:

  • Linux: top, htop, vmstat

  • Windows: Monitor de rendimiento (Performance Monitor)

  • SGBD: pg_stat_activity (PostgreSQL), SHOW PROCESSLIST (MySQL)

Buenas prácticas:

  • Revisar consultas costosas (EXPLAIN ANALYZE).

  • Evitar funciones o transformaciones innecesarias en las consultas.

  • Ajustar paralelismo (max_parallel_workers_per_gather en PostgreSQL, MAXDOP en SQL Server).


🔸 2. Memoria (RAM)

La memoria es clave para el caché y el procesamiento interno del SGBD.
Si es insuficiente, el motor recurre al disco, lo que degrada notablemente el rendimiento.

📘 Indicadores:

  • Uso de swap → señal de falta de memoria.

  • Alta actividad de disco por lectura de páginas → buffers pequeños.

Buenas prácticas:

  • Aumentar shared_buffers o innodb_buffer_pool_size.

  • Asignar suficiente work_mem para operaciones grandes.

  • Evitar ejecutar demasiadas consultas simultáneamente.


🔸 3. Disco (I/O)

El almacenamiento es el factor más lento en todo el sistema.
Las operaciones de escritura y lectura intensivas pueden ser un cuello de botella.

📘 Herramientas de monitoreo:

  • Linux: iostat, iotop

  • Windows: Resource Monitor

  • PostgreSQL: vistas pg_stat_io, pg_stat_bgwriter

Buenas prácticas:

  • Usar discos SSD o NVMe para reducir latencia.

  • Separar los archivos de datos, logs y backups en discos distintos.

  • Mantener índices actualizados (REINDEX, ANALYZE).


🔸 4. Entrada/Salida de Red

En sistemas distribuidos o aplicaciones web, la latencia de red puede impactar el tiempo total de las consultas.

Recomendaciones:

  • Minimizar el tráfico entre aplicación y base (consultas eficientes).

  • Usar connection pooling (p. ej. PgBouncer o HikariCP).

Monitorear latencias con ping, netstat o herramientas del proveedor cloud.

domingo, 1 de febrero de 2026

JSON Decoders de elm



El paquete elm/json nos proporciona el módulo Json.Decode. Está lleno de pequeños decodificadores que podemos conectar.

Para obtener "edad" de { "nombre": "Tom", "edad": 42 }, crearíamos un decodificador como este:


import Json.Decode exposing (Decoder, field, int)


ageDecoder : Decoder Int

ageDecoder =

  field "age" int


 -- int : Decoder Int

 -- field : String -> Decoder a -> Decoder a


La función campo toma dos argumentos:

String: el nombre del campo. Por lo tanto, solicitamos un objeto con un campo "edad".

Decoder a: el decodificador que se probará a continuación. Si el campo "edad" existe, probaremos este decodificador con el valor que contiene.

En resumen, el campo "edad" int solicita un campo "edad" y, si existe, ejecuta el Decoder Int para intentar extraer un entero.

Hacemos prácticamente lo mismo para extraer el campo "nombre":


import Json.Decode exposing (Decoder, field, string)


nameDecoder : Decoder String

nameDecoder =

  field "name" string


-- string : Decoder String


En este caso, solicitamos un objeto con un campo "nombre" y, si existe, queremos que su valor sea una cadena.


¿Y si queremos decodificar dos campos? Unimos los decodificadores con map2:


map2 : (a -> b -> value) -> Decoder a -> Decoder b -> Decoder value

Esta función toma dos decodificadores. Los prueba y combina sus resultados. Ahora podemos combinar dos decodificadores diferentes:


import Json.Decode exposing (Decoder, map2, field, string, int)


type alias Person =

  { name : String

  , age : Int

  }


personDecoder : Decoder Person

personDecoder =

  map2 Person

      (field "name" string)

      (field "age" int)


Por lo tanto, si usáramos personDecoder en { "name": "Tom", "age": 42 }, obtendríamos un valor Elm como Person "Tom" 42.

Si realmente quisiéramos adentrarnos en la esencia de los decodificadores, definiríamos personDecoder como map2 Person nameDecoder ageDecoder utilizando nuestras definiciones anteriores. ¡Siempre conviene construir los decodificadores a partir de bloques más pequeños!

Muchos datos JSON no son tan claros ni planos. Imagina si /api/random-quotes/v2 se hubiera publicado con información más completa sobre los autores:


{

  "quote": "December used to be a month but it is now a year",

  "source": "Letters from a Stoic",

  "author":

  {

    "name": "Seneca",

    "age": 68,

    "origin": "Cordoba"

  },

  "year": 54

}

Podríamos gestionar este nuevo escenario anidando nuestros decodificadores:

import Json.Decode exposing (Decoder, map2, map4, field, int, string)


type alias Quote =

  { quote : String

  , source : String

  , author : Person

  , year : Int

  }


quoteDecoder : Decoder Quote

quoteDecoder =

  map4 Quote

    (field "quote" string)

    (field "source" string)

    (field "author" personDecoder)

    (field "year" int)


type alias Person =

  { name : String

  , age : Int

  }


personDecoder : Decoder Person

personDecoder =

  map2 Person

    (field "name" string)

    (field "age" int)


Observe que no nos molestamos en decodificar el campo "origen" del autor. Los decodificadores no tienen ningún problema en omitir campos, lo que puede ser útil al extraer poca información de valores JSON muy grandes.

Json en Elm




Acabamos de ver un ejemplo que usa HTTP para obtener el contenido de un libro. Pero muchos servidores devuelven datos en un formato especial llamado Notación de Objetos JavaScript, o JSON.

Nuestro siguiente ejemplo muestra cómo obtener datos JSON, lo que nos permite pulsar un botón para mostrar citas aleatorias de una selección aleatoria de libros. 


import Browser

import Html exposing (..)

import Html.Attributes exposing (style)

import Html.Events exposing (..)

import Http

import Json.Decode exposing (Decoder, map4, field, int, string)


-- MAIN

main =

  Browser.element

    { init = init

    , update = update

    , subscriptions = subscriptions

    , view = view

    }


-- MODEL

type Model

  = Failure

  | Loading

  | Success Quote


type alias Quote =

  { quote : String

  , source : String

  , author : String

  , year : Int

  }


init : () -> (Model, Cmd Msg)

init _ =

  (Loading, getRandomQuote)


-- UPDATE

type Msg

  = MorePlease

  | GotQuote (Result Http.Error Quote)


update : Msg -> Model -> (Model, Cmd Msg)

update msg model =

  case msg of

    MorePlease ->

      (Loading, getRandomQuote)


    GotQuote result ->

      case result of

        Ok quote ->

          (Success quote, Cmd.none)


        Err _ ->

          (Failure, Cmd.none)


-- SUBSCRIPTIONS

subscriptions : Model -> Sub Msg

subscriptions model =

  Sub.none


-- VIEW

view : Model -> Html Msg

view model =

  div []

    [ h2 [] [ text "Random Quotes" ]

    , viewQuote model

    ]


viewQuote : Model -> Html Msg

viewQuote model =

  case model of

    Failure ->

      div []

        [ text "I could not load a random quote for some reason. "

        , button [ onClick MorePlease ] [ text "Try Again!" ]

        ]


    Loading ->

      text "Loading..."


    Success quote ->

      div []

        [ button [ onClick MorePlease, style "display" "block" ] [ text "More Please!" ]

        , blockquote [] [ text quote.quote ]

        , p [ style "text-align" "right" ]

            [ text "— "

            , cite [] [ text quote.source ]

            , text (" by " ++ quote.author ++ " (" ++ String.fromInt quote.year ++ ")")

            ]

        ]


-- HTTP

getRandomQuote : Cmd Msg

getRandomQuote =

  Http.get

    { url = "https://elm-lang.org/api/random-quotes"

    , expect = Http.expectJson GotQuote quoteDecoder

    }


quoteDecoder : Decoder Quote

quoteDecoder =

  map4 Quote

    (field "quote" string)

    (field "source" string)

    (field "author" string)

    (field "year" int)


Este ejemplo es bastante similar al anterior:

  • init inicia en el estado Cargando, con un comando para obtener una cita aleatoria.
  • update gestiona el mensaje GotQuote cuando hay una nueva cita disponible. Pase lo que pase, no tenemos comandos adicionales. También gestiona el mensaje MorePlease cuando alguien presiona el botón, emitiendo un comando para obtener más citas aleatorias.
  • view muestra las citas!

La principal diferencia está en la definición de getRandomCatGif. En lugar de usar Http.expectString, hemos cambiado a Http.expectJson. ¿Qué ocurre con esto?


Cuando se solicita a /api/random-quotes una cita aleatoria, el servidor produce una cadena JSON como esta:


{

"quote": "Diciembre solía ser un mes, pero ahora es un año",

"source": "Cartas de un estoico",

"author": "Séneca",

"year": 54

}


No ofrecemos garantías sobre la información aquí contenida. El servidor puede cambiar los nombres de los campos, y estos pueden tener diferentes tipos en distintas situaciones. ¡Es un mundo complejo!

En JavaScript, el enfoque consiste en convertir JSON en objetos JavaScript y esperar que todo salga bien. Pero si hay algún error tipográfico o datos inesperados, se produce una excepción de tiempo de ejecución en alguna parte del código. ¿Era incorrecto el código? ¿Eran incorrectos los datos? ¡Es hora de investigar para averiguarlo!

En Elm, validamos el JSON antes de que llegue a nuestro programa. Por lo tanto, si los datos tienen una estructura inesperada, lo detectamos de inmediato. Es imposible que datos incorrectos se filtren y provoquen una excepción de tiempo de ejecución tres archivos después. Esto se logra con decodificadores JSON.

sábado, 31 de enero de 2026

Optimización y Performance de base de datos

El rendimiento de una base de datos no depende solo de cómo están escritas las consultas o diseñadas las tablas, sino también de cómo está configurado el entorno en el que corre: el servidor, la memoria disponible, los discos, y los parámetros internos del SGBD.


🔹 Configuración del SGBD (Buffers y Caches)

Un SGBD moderno utiliza múltiples mecanismos de caché y buffering para reducir el acceso al disco (que es el componente más lento del sistema).

Optimizar estos parámetros es clave para lograr un rendimiento estable.


🔸 Buffer Pool / Shared Buffers

Es el área de memoria principal que el motor usa para guardar temporalmente páginas de datos leídas desde disco.

Cuanto más grande sea (dentro de lo razonable), menos lecturas físicas se necesitarán.

📘 Ejemplo (PostgreSQL):

SHOW shared_buffers;

-- Ejemplo: 128MB

📘 Configuración típica (postgresql.conf):

shared_buffers = 25% de la RAM total

📘 Ejemplo (MySQL / InnoDB):

innodb_buffer_pool_size = 2G

💡 Idealmente, el buffer pool debe poder contener los datos más consultados (tablas “calientes”).


🔸 Cache de Consultas (Query Cache)

Almacena los resultados de consultas recientes, evitando recalcularlas.

Muy útil en aplicaciones con consultas repetitivas y pocos cambios de datos.

📘 Ejemplo (MySQL):

SET GLOBAL query_cache_size = 64M;

💡 En sistemas con muchas escrituras, puede ser contraproducente (se invalida constantemente la caché).


🔸 Write-Ahead Log (WAL) / Redo Logs

Los SGBD guardan los cambios primero en un log de escritura secuencial antes de aplicarlos al disco.

Esto garantiza durabilidad (la D de ACID) y permite recuperación tras fallos.

📘 Parámetros importantes:

wal_buffers (PostgreSQL)

innodb_log_buffer_size (MySQL)

Aumentarlos puede mejorar el rendimiento de inserciones masivas o actualizaciones en bloque.


🔸 Checkpoints

Un checkpoint es el proceso que sincroniza los datos en memoria con el disco.

Si ocurre con demasiada frecuencia, puede generar picos de I/O; si ocurre muy poco, aumenta el riesgo de recuperación más lenta.

📘 Ejemplo (PostgreSQL):

checkpoint_timeout = 5min

checkpoint_completion_target = 0.9

💡 Ajustar según la frecuencia de escritura y la capacidad de disco.


viernes, 30 de enero de 2026

HTTP en elm

 


A menudo resulta útil obtener información de otras fuentes de internet.

Por ejemplo, supongamos que queremos cargar el texto completo de "Opinión Pública" de Walter Lippmann. Publicado en 1922, este libro ofrece una perspectiva histórica sobre el auge de los medios de comunicación y sus implicaciones para la democracia. Para nuestros propósitos, nos centraremos en cómo usar el paquete elm/http para integrar este libro en nuestro programa.

Si queres ver el ejemplo en acción, hace click en este link: https://elm-lang.org/examples/book


import Browser

import Html exposing (Html, text, pre)

import Http


-- MAIN


main =

  Browser.element

    { init = init

    , update = update

    , subscriptions = subscriptions

    , view = view

    }


-- MODEL


type Model

  = Failure

  | Loading

  | Success String


init : () -> (Model, Cmd Msg)

init _ =

  ( Loading

  , Http.get

      { url = "https://elm-lang.org/assets/public-opinion.txt"

      , expect = Http.expectString GotText

      }

  )


-- UPDATE


type Msg

  = GotText (Result Http.Error String)


update : Msg -> Model -> (Model, Cmd Msg)

update msg model =

  case msg of

    GotText result ->

      case result of

        Ok fullText ->

          (Success fullText, Cmd.none)


        Err _ ->

          (Failure, Cmd.none)


-- SUBSCRIPTIONS


subscriptions : Model -> Sub Msg

subscriptions model =

  Sub.none


-- VIEW


view : Model -> Html Msg

view model =

  case model of

    Failure ->

      text "I was unable to load your book."


    Loading ->

      text "Loading..."


    Success fullText ->

      pre [] [ text fullText ]


Algunas partes de esto deberían resultar familiares de ejemplos anteriores de la arquitectura Elm. Seguimos teniendo un modelo de nuestra aplicación. Seguimos teniendo una actualización que reacciona a los mensajes. Seguimos teniendo una función de vista que muestra todo en pantalla.

Las nuevas partes amplían el patrón principal que vimos antes con algunos cambios en init y update, y la incorporación de la suscripción.

La función init describe cómo inicializar nuestro programa:


init : () -> (Model, Cmd Msg)

init _ =

  ( Loading

  , Http.get

      { url = "https://elm-lang.org/assets/public-opinion.txt"

      , expect = Http.expectString GotText

      }

  )

Como siempre, debemos generar el modelo inicial, pero ahora también generamos un comando que indica lo que queremos hacer inmediatamente. Ese comando generará un mensaje que se introduce en la función de actualización.

Nuestro sitio web del libro se inicia en estado de carga y queremos obtener el texto completo. Al realizar una solicitud GET con Http.get, especificamos la URL de los datos que queremos obtener y qué datos esperamos que contengan. En nuestro caso, la URL apunta a datos del sitio web de Elm y esperamos que sea una cadena grande que podamos mostrar en pantalla.

La línea Http.expectString GotText indica algo más que esperamos una cadena. También indica que, cuando recibamos una respuesta, esta debe convertirse en un mensaje GotText:


type Msg

  = GotText (Result Http.Error String)


-- GotText (Ok "The Project Gutenberg EBook of ...")

-- GotText (Err Http.NetworkError)

-- GotText (Err (Http.BadStatus 404))


Tengamos en cuenta que usamos el tipo Result, mencionado en un par de secciones anteriores. Esto nos permite considerar completamente los posibles fallos en nuestra función de actualización. Hablando de funciones de actualización...

Nuestra función de actualización también devuelve un poco más de información:


update : Msg -> Model -> (Model, Cmd Msg)

update msg model =

  case msg of

    GotText result ->

      case result of

        Ok fullText ->

          (Success fullText, Cmd.none)


        Err _ ->

          (Failure, Cmd.none)


Al observar la firma de tipo, vemos que no solo devolvemos un modelo actualizado. También generamos un comando que indica lo que queremos que haga Elm.

Pasando a la implementación, realizamos la coincidencia de patrones con los mensajes de forma normal. Cuando llega un mensaje GotText, inspeccionamos el resultado de nuestra solicitud HTTP y actualizamos nuestro modelo según si fue un éxito o un fracaso. La novedad es que también proporcionamos un comando.

En caso de obtener el texto completo correctamente, ejecutamos Cmd.none para indicar que no hay más trabajo que hacer. ¡Ya obtuvimos el texto completo!

Y en caso de algún error, también ejecutamos Cmd.none y simplemente nos rendimos. El texto del libro no se cargó. Si quisiéramos ser más sofisticados, podríamos realizar la coincidencia de patrones con Http.Error y reintentar la solicitud si se agota el tiempo de espera o algo similar.

La cuestión es que, independientemente de cómo decidamos actualizar nuestro modelo, también podemos ejecutar nuevos comandos. ¡Necesito más datos! ¡Quiero un número aleatorio! Etc.

La otra novedad de este programa es la función de suscripción. Permite consultar el modelo y decidir si se desea suscribirse a cierta información. En nuestro ejemplo, usamos Sub.none para indicar que no necesitamos suscribirnos a nada, pero pronto veremos un ejemplo de un reloj al que queremos suscribirnos a la hora actual.

Al crear un programa con Browser.element, configuramos un sistema como este:

Podemos ejecutar comandos desde init y update. Esto nos permite, por ejemplo, realizar solicitudes HTTP cuando queramos. También podemos suscribirnos a información interesante. (¡Veremos un ejemplo de suscripciones más adelante!)