home about projects technology contact

Technology

In this page, I want to share a thought and then list some technology tools I have learned during my career.

A thought about technology I'd like to share

I really enjoy learning new technologies. This has been one of my key motivations for getting a better understanding of what they do, how they work, its consequences and limitations. I believe technology simplifies our work and is one of humanity's assets. I think as humanity we should have guidelines for its usage. I envision a world where humankind agrees on a universal reference that encompasses all technologies we have created, highlighting their uses and their consequences classified by topics. Misinformation and ignorance around new tech developments will be prevented, allowing us to use it responsibly. As of today (07-2026), the LLMs are our "new gadget", bringing a similar hype to what social networks brought in the 2010s and the internet during the 90s-2000s. I believe in the decades to come we will see discussions around questionable new technologies' uses and problems concerning their responsible usage. I also believe technology should be decentralised, as it belongs to humankind. Great developments have been made when technology tools have been shared and new insights are discovered or proposed when those are used by diverse groups.

Technologies I've used during my career

Here I list technologies I enjoy using and learning:

$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
          

AWS & friends

I have taught courses in AWS at ITAM for services such as:

  • Route 53
  • Rekognition
  • S3
  • EC2
  • Autoscaling Groups
  • RDS

I have used AWS at CONABIO and biometrio.earth for data processing and high performance computing. I have used large clusters (around 100 computing instances) for generating national land cover maps. I also have used the Google's Cloud Platform and Microsoft's 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;
}
          

C & friends

I taught courses in C at ITAM in the parallel computing and numerical optimization context. I have combined C with Python (Cython) and R (Rcpp) to get higher efficiency in execution times.


$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
          

Docker

The Nginx, the DogVentures mobile app, the web app, Hashicorp Vault, the frontend, backend services described in this website under "Personal Projects" are running as Docker containers.

$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'
          

Domain, DNS & Certificate management

I have used deSEC, myaddr.tools and Let's Encrypt certbot to setup a domain for this website and another domain with its certificates for the Nginx, the DogVentures mobile app, the web app, the frontend, backend services described in this website under "Personal Projects".

$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
          

Git

I have used Git in so many projects and places that I believe it is part of my first set of CLI tools that I used in the terminal (alongside with grep, awk, sed,...). I use version control in other tools that extend Git or its core idea outside of files (such as ArgoCD and 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"
}

          

Go

Currently learning Go to deploy microservices.

$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
          

Hashicorp Vault

When I deployed my Nginx, which is used to call the Ollama (LLM) server, I noticed lots of bots and crawlers, were trying to get access to files. The first files they wanted to read were ".env, robots.txt, ~/.aws/credentials, ..." so I used Hashicorp Vault and let my services query it to get the credentials.

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";
    }
}
	  

Java & friends

I learned Java when I was a student at ITAM. I used Java to update the mobile app's (legacy code) when working at biometrio.earth. I recently have been learning modern frameworks for efficient object management and efficient runtime such as Spring, Micronaut and 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")
    }
  })
})

          

JavaScript & friends

I created this website with HTML, CSS and JS (vanilla). I also know Node, Express, TypeScript, React and Angular. One of the docker containers in the app described in this website under "Personal Projects", uses TypeScript, Express, Vite for the frontend, backend and API. The example video shows how the app uses the OAuth 2.0 authorization code flow to make a POST request to the 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
          

Kafka

I have used Kafka for asynchronous communication between the microservices in the DogVentures mobile app which is described on this website under "Personal Projects", and when using the OAuth 2.0 authorization code to call the 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()
	    }
	}
      }
    }
}
          

Kotlin & friends

I have been learning Dart and Kotlin for mobile programming, and in this case I'm showing an extract of the DogVentures mobile app, which is shown on this website under "Personal Projects". This app uses the Jetpack compose framework.

$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
          

Kubernetes

I have used Kubernetes in both CONABIO and biometrio.earth to scale up and down the computing cluster. I used kops for Kubernetes cluster management infrastructure and connected it to AWS for high-performance computing (around 100 computing instances) for generating land cover maps. When working at CONABIO and biometrio.earth I used Kubeflow in combination with Kale to implement artificial intelligence (AI) and machine learning (ML) pipelines for data processing. Some examples of these results are shown in this website under "Projects" then "biometrio.earth" (species animal detection) and "CONABIO" (land cover maps).

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;

    }
}
          

Nginx

I use Nginx as a reverse proxy for services. Here in the code I'm using it for the domain I got with myaddr.tools, which is being used for the OAuth 2.0 authorization Code in my mobile and web apps that are shown on this website under "Personal Projects."

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
          

Python & friends

I taught courses in Python at ITAM for numerical optimization, and I used it at CONABIO and biometrio.earth for file management and artificial intelligence (AI) and (ML) model usage. In the past few months I have been learning Langchain with Python but also outside of it (such as Lanchain-JS). I also know other high level languages as R which I used as a student. I published my master's thesis with a computer program written in R to implement the support vector machine (IA and ML algorithm) with parallel computing. I taught courses in R at ITAM. In my numerical methods courses at ITAM, I compared execution times of implementations between C, Julia, R and Python.

$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

          

Snyk

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'
          

SQL

I have used SQL in different places and projects. We used PostgreSQL at biometrio.earth to store our structured data. I normally use SDKs to make queries, such as, psycopg3, SQLAlchemy, and extensions such as PostGIS.