En esta página, quiero compartir un pensamiento y luego listar algunas herramientas tecnológicas que he aprendido durante mi carrera.
Me encanta aprender tecnologías nuevas. Esto ha sido una de mis motivaciones clave para comprender mejor qué hacen, cómo funcionan, cuáles son sus consecuencias y sus limitaciones. Creo que la tecnología simplifica nuestro trabajo y es uno de los recursos de la humanidad. Pienso que, como humanidad, deberíamos tener guías para su uso. Imagino un mundo en el que la humanidad acuerde una referencia universal que abarque todas las tecnologías que hemos creado, destacando sus usos y sus consecuencias clasificadas por temas. Se evitaría la desinformación y la ignorancia sobre los desarrollos de nuevas tecnologías, lo que nos permitiría usarlas de manera responsable. A la fecha (07-2026), los LLMs son nuestro “nuevo gadget”, trayendo un entusiasmo similar al que las redes sociales trajeron en la década de 2010 y el internet durante los años 90-2000. Creo que en las próximas décadas veremos discusiones sobre los usos y problemas de tecnologías nuevas cuestionables relacionados con su uso responsable. También creo que la tecnología debería ser descentralizada, porque pertenece a la humanidad. Se han logrado grandes avances cuando las herramientas tecnológicas se comparten y surgen nuevos aprendizajes o propuestas al ser utilizadas por grupos diversos.
A continuación enlisto las tecnologías que disfruto utilizar y aprender:
$aws iam create-group --group-name kops
$aws iam attach-group-policy --policy-arn \
arn:aws:iam::aws:policy/AmazonEC2FullAccess --group-name kops
$aws iam attach-group-policy --policy-arn \
arn:aws:iam::aws:policy/AmazonRoute53FullAccess --group-name kops
$aws iam attach-group-policy --policy-arn \
arn:aws:iam::aws:policy/AmazonS3FullAccess --group-name kops
$aws iam attach-group-policy --policy-arn \
arn:aws:iam::aws:policy/IAMFullAccess --group-name kops
$aws iam attach-group-policy --policy-arn \
arn:aws:iam::aws:policy/AmazonVPCFullAccess --group-name kops
$aws iam attach-group-policy --policy-arn \
arn:aws:iam::aws:policy/AmazonElasticFileSystemFullAccess --group-name kops
Impartí cursos de AWS en el ITAM para uso de servicios como:
He usado AWS en CONABIO y en biometrio.earth para el procesamiento de datos y el cómputo de alto desempeño. He usado clústeres grandes (alrededor de 100 instancias de cómputo) para generar mapas nacionales de cobertura del suelo. También he usado la Google Cloud Platform y Microsoft Azure.
#include< stdio.h >
#include< stdlib.h >
#include< math.h >
int main(int argc, char *argv[]){
double aprox;
double factor = 1.0;
double sum = 0.0;
double objetivo = M_PI;
int k;
int n=1000;
long conteo_threads;
conteo_threads = strtol(argv[1], NULL, 10);
# pragma omp parallel for num_threads(conteo_threads) default(none) \
reduction(+:sum) private(k, factor) shared(n)
for(k=0;k < n;k++){
factor=(k%2==0)?1.0:-1.0;
sum+=factor/(2*k+1);
}
aprox = 4.0*sum;
printf("estimación de pi %.15e\n", aprox);
printf("error relativo: %.15e\n", fabs(objetivo-aprox)/fabs(objetivo));
return 0;
}
Impartí cursos de C en ITAM en el contexto de computación paralela y optimización numérica. He combinado C con Python (Cython) y R (Rcpp) para programas de computadora eficientes para mejorar tiempos de ejecución.
$docker build -t ledaps/ledaps:v1 .
$docker run --rm -v :/opt/ledaps \
-v :/data \
-v :/results \
ledaps/ledaps:v1 /results/shell_script /data/ /opt/ledaps
$docker run --rm -v /Users/ledaps/ancillary_data/:/opt/ledaps \
-v /Users/data/:/data \
-v $(pwd):/results \
ledaps/ledaps:v1 /results/shell_script /data/LE70210481999203AGS00/ /opt/ledaps
El Nginx, la app móvil DogVentures, la app web, Hashicorp Vault y los servicios frontend, backend que se describen en este sitio web bajo "Proyectos Personales" están ejecutándose como contenedores de Docker.
$docker run -it --rm -v "$dir/certbot-etc:/etc/letsencrypt" \
-v "$dir/certbot-var:/var/log/letsencrypt" \
-v "$dir/myaddr-auth.sh:/myaddr-auth.sh:ro" \
-v "$dir/myaddr-cleanup.sh:/myaddr-cleanup.sh:ro" \
-e MYADDR_KEY="$key" \
certbot-myaddr certonly \
--manual \
--manual-auth-hook /myaddr-auth.sh \
--manual-cleanup-hook /myaddr-cleanup.sh \
--preferred-challenges dns \
--non-interactive \
--agree-tos \
--email $email \
-d revproxy.myaddr.io \
-d '*.revproxy.myaddr.io'
He usado deSEC, myaddr.tools y Let's Encrypt certbot para configurar el dominio de este sitio web y otro dominio con sus certificados para el Nginx, la app móvil "DogVentures", la app web y los servicios frontend, backend que se describen en este sitio web bajo "Proyectos Personales".
$git config --global gpg.format openpgp
$git config --global user.signingkey my-signin-key
$git config --global commit.gpgsign true
$git clone ssh://git@git-host.org/myuser/my-repository.git
$git add file
$git commit -m "message" -i "file"
$git push -u origin my-branch
He usado Git en muchísimos proyectos y lugares, y creo que forma parte del primer conjunto de herramientas CLI que utilicé en la terminal (junto con grep, awk, sed, ...). Uso control de versiones en otras herramientas que extienden Git o que utilizan la idea central de Git más allá de los archivos (como Argo y DVC).
package main
import "fmt"
func main(){
cards := []string{newCard(), "Ace of Spades", "Five of Diamonds"}
cards = append(cards, "Ace of Clubs")
fmt.Println(cards)
println("Iterating over a slice")
for i,card := range cards {
fmt.Println(i, card)
}
}
func newCard() string{
return "Ace of Diamonds"
}
Actualmente aprendiendo Go para despliegue de microservicios.
$vault status
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 5
Threshold 3
Version 2.0.0
Build Date 2026-04-13T18:49:01Z
Storage Type file
Cluster Name vault-cluster-830152dd0
Cluster ID 29o92ke2-4184-9812-381l-8ol2s2k83o2e
HA Enabled false
$vault secrets enable -path=mypath-secrets -version=2 kv
Success! Enabled the kv secrets engine at: auth0-secrets/
$vault kv put mypath-secrets/myproject/webapp \
DOMAIN="myissuer.mx.myprovider.com" \
CLIENT_ID="lai3Elkao01lkajsdflkjasdeoijasdo" \
CLIENT_SECRET="0JDLKEJASFL8qOEl2la3oasdfane-aleixxi_QEOIDAnasdlflaskejl_albjox2"
============ Secret Path ============
mypath-secrets/data/myproject/webapp
======= Metadata =======
Key Value
--- -----
created_time 2026-06-18T06:35:29.502223125Z
custom_metadata
deletion_time n/a
destroyed false
version 1
Cuando implementé mi Nginx, que se utiliza para llamar al servidor de Ollama (LLM), noté muchos bots y rastreadores que intentaban acceder a archivos. Los primeros archivos que querían leer eran “.env, robots.txt, ~/.aws/credentials, ...”, así que usé Hashicorp Vault y permití que mis servicios le consultaran para obtener las credenciales.
import java.util.Calendar;
import java.util.TimeZone;
import androidx.fragment.app.Fragment;
public class PreSettingsFragment extends Fragment {
private static final String TAG ="PreSettingsFragment";
final int MEXICO_UTC_MINUS_6 = -6; // Config - Mexico (Mexico City) UTC -6
final int GERMANY_UTC_PLUS_1 = 1; // Config - Germany UTC +1 (Winter Time)
public String getFileNameByTimeZone() {
long currentTimeMillis = Calendar.getInstance().getTimeInMillis();
// Get the time difference in milliseconds, considering daylight saving time.
int offsetMillis = TimeZone.getDefault().getOffset(currentTimeMillis);
// Convert the difference to hours
int offsetHours = offsetMillis / (1000 * 60 * 60);
switch (offsetHours) {
case MEXICO_UTC_MINUS_6:
return "ultrasonic_mexico.json";
case GERMANY_UTC_PLUS_1:
return "ultrasonic_germany.json";
default:
return "ultrasonic.json";
}
}
Aprendí Java cuando era estudiante en el ITAM. Utilicé Java para actualizar la app de móvil (legacy code) cuando trabajé con biometrio.earth. Recientemente he estado aprendiendo frameworks modernos para manejo de objetos y ejecución eficiente como Spring, Micronaut y Quarkus.
document.addEventListener("DOMContentLoaded",()=>{
const btn = document.getElementById(("btn-language"));
const listLanguages = document.getElementById("list-languages");
if(btn && listLanguages){
btn.addEventListener('click', ()=>{
listLanguages?.classList.toggle("language-menu")
})
}
document.addEventListener('click', (e) => {
if(!listLanguages?.contains(e.target) && !btn?.contains(e.target)){
listLanguages?.classList.add("language-menu")
}
})
})
Creé este sitio con HTML, CSS y JS (vanilla). También conozco Node, Express, TypeScript, React y Angular. Uno de los contenedores de docker en la app que se describe en este sitio web bajo "Proyectos Personales" usa Express, TypeScript y Vite para el frontend, backend y API. El video ejemplo muestra cómo la app usa el flujo de código de autorización de OAuth 2.0 para hacer una solicitud POST a la API.
$kafka-topics --create --topic messagesorder --partitions 2 --replication-factor 2 --bootstrap-server myhost:9094
Created topic messagesorder.
$kafka-topics --describe --topic messagesorder --bootstrap-server myhost:9094
Topic: messagesorder TopicId: lQWXBUuXRAS70rNGvLWHig PartitionCount: 2 ReplicationFactor: 2 Configs: min.insync.replicas=1
Topic: messagesorder Partition: 0 Leader: 1 Replicas: 1,2 Isr: 1,2 Elr: LastKnownElr:
Topic: messagesorder Partition: 1 Leader: 2 Replicas: 2,1 Isr: 2,1 Elr: LastKnownElr:
$kafka-console-consumer --topic messagesorder --partition 0 --bootstrap-server myhost:9094 --offset "earliest" --formatter-property print.key=true
1 ble
$kafka-console-producer --bootstrap-server myhost:9094 --topic messagesorder --reader-property "parse.key=true" --reader-property "key.separator=:"
>1:ble
He usado Kafka para la comunicación asíncrona entre los microservicios en la app móvil DogVentures, que se describe en este sitio web bajo "Proyectos Personales", y también al usar el flujo de autorización por código OAuth 2.0 para llamar a la API.
@Composable
fun CreateCard() {
var buttonState by remember{ mutableStateOf(false) }
Surface(
modifier = Modifier.fillMaxWidth().fillMaxHeight()
) {
Card(
modifier = Modifier
.width(200.dp)
.height(390.dp)
.padding(12.dp),
shape = RoundedCornerShape(corner = CornerSize(15.dp)),
colors = CardDefaults.cardColors(
containerColor = Color.White
),
elevation = CardDefaults.cardElevation(
defaultElevation = 4.dp
)
)
{
Column(
modifier = Modifier
.height(800.dp)
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(15.dp),
horizontalAlignment = Alignment.CenterHorizontally
)
{
CreateImageProfile()
HorizontalDivider(thickness = 5.dp, color = Color(0xFF800080))
CreateInfo()
Button(
onClick = {
buttonState = !buttonState
},
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF800080))
)
{
Text(text = "My Dogventures",
fontSize = 20.sp
)
}
if (buttonState){
ButtonContent()
}
}
}
}
}
He estado aprendiendo Dart y Kotlin para programación en móvil, y en este caso estoy mostrando un extracto de la app móvil DogVentures, que se muestra en este sitio web bajo "Proyectos Personales". Esta app utiliza el framework de Jetpack compose.
$kubectl create -f efs-provisioner.yaml
pv_id=$(kubectl get pv|grep pvc | cut -d' ' -f1)
$kubectl patch pv $pv_id -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
$kubectl scale deployments/efs-provisioner --replicas=0
$kops create cluster \
--name=${CLUSTER_FULL_NAME} \
--zones=${CLUSTER_AWS_AZ} \
--master-size="t2.medium" \
--node-size="t2.medium" \
--node-count="3" \
--dns-zone=${DOMAIN_NAME} \
--ssh-public-key="/home/ubuntu/.ssh/id_rsa.pub" \
--kubernetes-version=${KUBERNETES_VERSION} --yes
$kops validate cluster
He usado Kubernetes tanto en CONABIO como en biometrio.earth para escalar hacia arriba y hacia abajo el clúster de cómputo. Usé kops para la infraestructura de administración de clústeres de Kubernetes y la conecté a AWS para cómputo de alto desempeño (alrededor de 100 instancias de cómputo) para generar mapas de cobertura de suelo. Cuando trabajé en CONABIO y en biometrio.earth, utilicé Kubeflow y Kale para implementar flujos de procesamiento de inteligencia artificial (IA) y aprendizaje automático (ML). Algunos ejemplos de estos resultados están en este sitio web bajo "Proyectos" y "biometrio.earth" (detección de especies animales) y "CONABIO" (mapas de cobertura de suelo).
map $http_origin $cors_origin {
default "";
"~^https://ff\.revproxy\.myaddr\.dev$" "$http_origin";
"~^https://api\.ff\.revproxy\.myaddr\.dev$" "$http_origin";
}
server{
listen 80;
listen [::]:80;
server_name ff.revproxy.myaddr.dev;
port_in_redirect off;
location /{
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name ff.revproxy.myaddr.dev;
ssl_certificate /etc/nginx/certs/server.pem;
ssl_certificate_key /etc/nginx/certs/server-key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
add_header 'X-Frame-Options' 'SAMEORIGIN' always;
add_header 'X-Content-Type-Options' 'nosniff' always;
add_header 'Referrer-Policy' 'strict-origin-when-cross-origin' always;
add_header 'Strict-Transport-Security' 'max-age=31536000; includeSubDomains' always;
add_header 'Content-Security-Policy' "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self' https://api.ff.revproxy.myaddr.dev; img-src 'self' data: https:; font-src 'self' data:;" always;
set $frontend_backend http://ff_cred_project:3010;
location / {
limit_req zone=frontend_limit burst=20 nodelay;
proxy_pass http://ff_cred_project:3010;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Cookie $http_cookie;
proxy_pass_header Set-Cookie;
proxy_buffering off;
add_header 'Strict-Transport-Security' 'max-age=31536000; includeSubDomains' always;
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'X-Frame-Options' 'SAMEORIGIN' always;
add_header 'X-Content-Type-Options' 'nosniff' always;
add_header 'Content-Security-Policy' "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self' https://api.ff.revproxy.myaddr.dev; img-src 'self' data: https:; font-src 'self' data:;" always;
}
}
Uso Nginx como un reverse proxy para servicios. Aquí en el código lo estoy usando para el dominio que obtuve con myaddr.tools, el cual se utiliza para el flujo de autorización por código de OAuth 2.0 en mis apps móviles y web, que se muestran en este sitio web bajo "Proyectos Personales".
def move_files_to_standard_dir(shortname_project,
standard_dir,
input_directory,
dict_source,
bucket_name,
dry_run,
hash_project_name,
date_of_deployment):
"""
Returns dictionary that will be written on disk
"""
iterator = multiple_file_types(input_directory,
SUFFIXES_BE)
dict_output_metadata = {}
dict_output_metadata["Project"] = shortname_project
dict_output_metadata["MetadataDevice"] = dict_source["MetadataDevice"]
dict_output_metadata["DaysBetweenFirstAndLastDate"] = dict_source["DaysBetweenFirstAndLastDate"]
brand = dict_source["MetadataDevice"]["Brand"]
dict_output_metadata["MetadataFiles"] = {}
dict_output_metadata["Datetimes"] = {}
dict_output_metadata["Dates"] = {}
for filename in iterator:
f_pathlib = pathlib.Path(filename)
f_pathlib_suffix = f_pathlib.suffix
filename_md5 = md5_for_file(filename) #md5 will be basename of filename
if f_pathlib_suffix in SUFFIXES_BE_IMAGES and brand == "Camara RECONYX": #make a query to Balam DB to retrieve string "Camara RECONYX"
filename_number = re.findall("([0-9]{1,}).[JPG|AVI]", f_pathlib.name)[0] #get 0074 of RCNX0074.JPG
filename_std = "".join([filename_md5,
"_",
filename_number,
f_pathlib_suffix])
logger.info("File {} will be moved to: {} with name {}",filename, standard_dir,filename_std)
else:
filename_std = "".join([filename_md5,
f_pathlib_suffix])
logger.info("File {} will be moved to: {} with name {}",filename, standard_dir,filename_std)
dst_filename = os.path.join(standard_dir, filename_std)
dst_filename_s3 = get_dst_filename_for_s3(dst_filename,
bucket_name)
dict_output_metadata["MetadataFiles"][dst_filename_s3] = dict_source["MetadataFiles"][filename]
dict_output_metadata["Datetimes"][dst_filename_s3] = dict_source["Datetimes"][filename]
dict_output_metadata["Dates"][dst_filename_s3] = dict_source["Dates"][filename]
if not dry_run:
try:
f_pathlib.rename(dst_filename) #move
except:
try:
shutil.move(filename, dst_filename)
except:
dict_output_metadata = None
if dict_output_metadata is not None:
insert_original_and_new_name_in_processedfiles_table(shortname_project,
input_directory,
f_pathlib.name,
filename_std,
standard_dir,
hash_project_name,
bucket_name,
date_of_deployment)
return dict_output_metadata
Impartí cursos de Python en el ITAM para optimización numérica, y lo usé en CONABIO y en biometrio.earth para manejo de archivos y uso de modelos de inteligencia artificial (IA) y aprendizaje automático (ML). En los últimos meses he estado aprendiendo Langchain con Python, pero también fuera de él (por ejemplo, Langchain-JS). También conozco otros lenguajes de alto nivel como R que utilicé siendo estudiante. Publiqué mi tesis de maestría con un programa de computadora escrito en R para implementar la máquina de soporte vectorial (algoritmo de IA y ML) con cómputo en paralelo. Enseñé cursos de R en el ITAM. En los cursos de métodos numéricos en el ITAM, comparé tiempos de ejecución de implementaciones hechas en C, Julia, R y Pyton.
$SNYK_TOKEN="aldkfja12ljoaesfoi"
$DIR=/Users/erick/web-app
$docker run --name snyk_example --platform linux/amd64 --rm -it \
--env SNYK_TOKEN -v $DIR:/app snyk/snyk:node-24
$docker exec -it snyk_example bash
$snyk auth $SNYK_TOKEN
$snyk test --show-vulnerable-paths=all
I have used Snyk to analyze the webapp I developed and it is described in this website under "Personal Projects" to explore its vulnerabilities.
SELECT
"Sites".identifier,
"SamplingAreas".identifier,
"SamplingPoints".metadata ->> 'sd_card' AS "SD_Card",
"ProjectDevices".project_serial_number,
"ProjectDevices".device_type,
"SamplingPoints".device_config,
ST_Y(location) as "Latitude",
ST_X(location) as "Longitude",
date_deployment,
"SamplingPoints".metadata ->> 'monitor' AS "Monitor",
"SamplingPoints".metadata ->> 'token' AS "Token"
FROM "SamplingPoints"
INNER JOIN "ProjectDevices" ON "ProjectDevices".id = "SamplingPoints".device_id
INNER JOIN "SamplingAreas" ON "SamplingAreas".id = "SamplingPoints".sampling_area_id
INNER JOIN "Sites" ON "Sites".id = "SamplingPoints".site_id
WHERE date_deployment > '2023-11-01'
He usado SQL en distintos lugares y proyectos. En biometrio.earth usamos PostgreSQL para almacenar nuestros datos estructurados. Normalmente uso SDKs para hacer consultas, como psycopg3, SQLAlchemy, y extensiones como PostGIS.