domingo, 29 de junio de 2025

Vibe Coding (la programación vía IA): ¿el futuro de la programación?

Vibe Coding es un nuevo paradigma de programación, una nueva forma de crear código.

Es un enfoque emergente en el desarrollo de software donde la programación se realiza principalmente a través de interacciones en lenguaje natural con una inteligencia artificial, en lugar de escribir código línea por línea. El término fue popularizado por Andrej Karpathy en 2025 y se basa en la idea de que "ver cosas, decir cosas, ejecutar cosas y copiar/pegar cosas" puede ser suficiente para construir software funcional.

¿Cómo funciona?

En lugar de escribir código manualmente, describes lo que quieres lograr —por texto o incluso por voz— y la IA genera el código por ti. Si algo falla, simplemente le explicas el error a la IA y le pides que lo corrija. Es un flujo de trabajo iterativo, conversacional y muy ágil.

En otras palabras:

Una persona, ya sea programador o no (ojo aquí), podrá crear aplicaciones. Esto gracias a la Inteligencia Artificial.

Esto lo vemos en pleno 2025 donde el uso de herramientas como ChatGPT, Grok, Copilot o DeepSeek se ha vuelto más habitual de lo que pensamos.

¿Significará el fin de los programadores? Si y no.

Si, para los programadores que se duermen en sus laureles. Quienes no se preparan constantemente, que no investigan, que se aferran a lo que ya conocen.

No, para los que constantemente crean código. Los que tienen curiosidad por investigar nuevas tecnologías. Los que programan incluso en lenguajes poco comerciales. Los que crean contenido y no son solo consumidores.

Pros y contras del Vibe Coding (o programar vía I.A.)

Pros:

Para quienes saben programar será de mucha ayuda. Automatizará el trabajo que antes erá muy tedioso. Reducirá el tiempo de desarrollo.

Para quienes no saben programar será de ayuda para "minimizar" la complejidad técnica. Esto para ciertas cosas, cabe aclarar.

Contras:

Para quienes saben programar les hará más difícil el mantenimiento de código. Tendrán que rehacer y corregir código inservible arrojado por la I.A., tendrán que revisar lo que es verdaderamente útil y descartar lo que no. La deuda técnica se volverá el pan de cada día.

Para quienes no saben programar les hará creer que son programadores. Creerán que el código arrojado por la I.A. resolverá todo su trabajo, no será así. Tendrán muchos problemas aún mayores. Si desconocen el lenguaje con el que se la I.A. "creó", el código será como entender un idioma indescifrable. Peor cuando salga algún "bug" y no tengan idea de cómo remediarlo, ya que la misma I.A. arrojará resultados nada útiles.

En conclusión: el Vibe Coding es un paradigma para acelerar el desarrollo y "democratizar" la programación, pero requiere criterio técnico para evitar errores, dependencias o problemas de calidad.

Enlaces:

https://codemonkeyjunior.blogspot.com/2025/04/firebase-studio-el-visual-studio-code.html
https://codemonkeyjunior.blogspot.com/2025/03/como-ser-un-mejor-programador-en-un.html

sábado, 28 de junio de 2025

Subir un archivo CSV a una tabla en GCP BigQuery

En una entrega pasada vimos como cargar datos desde un CSV a una tabla en BigQuery.

Esta vez crearemos un programa con Java y BigQuery. Para ello necesitamos tener un archivo con datos el cual llamaremos "DATOS.txt". El contenido del archivo será similar a esto:

20250412,34,450.0
20250412,34,432.0
20250412,122,500.0

Tendremos una tabla llamada ``tkdata`` con los siguientes campos:

fchinf DATE 
numregs INT64 
valor STRING 

Requisitos:

  • Tener acceso a GCP BigQuery.
  • Tener JDK 11 o más actual. 
  • Tener Maven (el más actual).

¿Qué hará el programa?

  1. Verificar si existe el archivo a subir ("DATOS.txt"). 
  2. Obtener su contenido y guardarlo en una lista tipo String. 
  3. Crear un objeto tipo StringBuilder a partir de la lista tipo String. 
  4. Enviar el contenido del objeto StringBuilder a un nuevo archivo ("tkdata.csv") que se guardará en el mismo bucket del archivo original. 
  5. Cargar el contenido del nuevo archivo a la tabla ``tkdata``.
  6. Verificar que los datos hayan sido cargados a la tabla.

BigQueryCsvUploader.java

import com.google.cloud.bigquery.*;
import com.google.cloud.storage.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class BigQueryCsvUploader {
    private static final Logger LOG = LoggerFactory.getLogger(BigQueryCsvUploader.class);
    private static final String NAME_FILE_ORIGINAL = "DATOS.csv";
    private static final String TABLE_NAME = "tkdata";
    private static final String DATASET = "mydataset";
    private static final String PROJECT = "myproject";
    private static final String BUCKET = "mybucket";
    private static final String NAME_NEW_FILE = "tkdata.csv";
    private static final long MAX_SIZE_BYTES = 300 * 1024 * 1024; // 300 MB

   
    public static class Tkdata {
        private Date fchinf;
        private long numregs;
        private String valor;

        public Date getFchinf() { return fchinf; }
        public void setFchinf(String fchinf) throws ParseException {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            this.fchinf = sdf.parse(fchinf);
        }
        public long getNumregs() { return numregs; }
        public void setNumregs(String numregs) { this.numregs = Long.parseLong(numregs); }
        public String getValor() { return valor; }
        public void setValor(String valor) { this.valor = valor; }
    }

   
    public static boolean existFile(String bucketName, String fileName) {
        Storage storage = StorageOptions.getDefaultInstance().getService();
        Blob blob = storage.get(BlobId.of(bucketName, fileName));
        return blob != null && blob.exists();
    }

   
    public static boolean maxSize(String bucketName, 
     String fileName, long maxSizeBytes) {
        Storage storage = StorageOptions.getDefaultInstance().getService();
        Blob blob = storage.get(BlobId.of(bucketName, fileName));
        if (blob == null) {
            LOG.error("El archivo {} no existe en el bucket {}", fileName, bucketName);
            return false;
        }
        return blob.getSize() <= maxSizeBytes;
    }

    
    public static List<Tkdata> getListTkdata(String bucketName, String fileName) {
        List<Tkdata> listaTkdata = new ArrayList<>();
        Storage storage = StorageOptions.getDefaultInstance().getService();
        Blob blob = storage.get(BlobId.of(bucketName, fileName));
        if (blob == null) {
            LOG.error("Archivo{} no encontrado en el bucket {}", fileName, bucketName);
            return listaTkdata;
        }

        String content = new String(blob.getContent(), StandardCharsets.UTF_8);
        try (BufferedReader reader = new BufferedReader(new StringReader(content))) {
            String line;
            reader.readLine();
            while ((line = reader.readLine()) != null) {
                String[] parts = line.split(",", -1); 
                if (parts.length == 3) {
                    Tkdata obj = new Tkdata();
                    try {
                        obj.setFchinf(parts[0].trim());
                        obj.setNumregs(parts[1].trim());
                        obj.setValor(parts[2].trim());
                        listaTkdata.add(obj);
                    } catch (ParseException | NumberFormatException e) {
                        LOG.error("Error parseando linea: {}", line, e);
                    }
                } else {
                    LOG.warn("Linea malformada: {}", line);
                }
            }
        } catch (IOException e) {
            LOG.error("Error al leer el contenido", e);
        }
        return listaTkdata;
    }

    
    public static StringBuilder convertToStringBuilder(List<Tkdata> listaTkdata) {
        StringBuilder sb = new StringBuilder();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        for (Tkdata item : listaTkdata) {
            sb.append(sdf.format(item.getFchinf())).append(",");
            sb.append(item.getNumregs()).append(",");
            sb.append(item.getValor()).append("\n");
        }
        return sb;
    }

   
    public static boolean creaNuevoFileCSV(String bucketName, 
       String fileName, String content) {
        try {
            Storage storage = StorageOptions.getDefaultInstance().getService();
            BlobId blobId = BlobId.of(bucketName, fileName);
            BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("text/csv").build();
            storage.create(blobInfo, content.getBytes(StandardCharsets.UTF_8));
            return true;
        } catch (Exception e) {
            LOG.error("Error al subir archivo {} al bucket {}", fileName, bucketName, e);
            return false;
        }
    }

    
    public static boolean loadCSVToTableBigQuery(String projectId, 
     String datasetId, String bucketName, String sourceFileName, String tableName) {
        try {
            BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService();
            TableId tableId = TableId.of(projectId, datasetId, tableName);

            
            Schema schema = Schema.of(
                Field.of("fchinf", StandardSQLTypeName.DATE),
                Field.of("numregs", StandardSQLTypeName.INT64),
                Field.of("valor", StandardSQLTypeName.STRING)
            );

           
            JobConfiguration jobConfig = LoadJobConfiguration.newBuilder(
                tableId,
                String.format("gs://%s/%s", bucketName, sourceFileName),
                FormatOptions.csv()
            )
                .setSchema(schema)
                .setSkipLeadingRows(0) 
                .setWriteDisposition(JobInfo.WriteDisposition.WRITE_APPEND) 
                .build();

            
            Job job = bigquery.create(JobInfo.of(jobConfig));
            job = job.waitFor();
            if (job.isDone() && job.getStatus().getError() == null) {
                LOG.info("CSV {} successfully loaded into BigQuery table {}.{}", 
                 sourceFileName, datasetId, tableName);
                return true;
            } else {
                LOG.error("Error cargando CSV a BigQuery: {}", job.getStatus().getError());
                return false;
            }
        } catch (Exception e) {
            LOG.error("Error en el proceso de carga", e);
            return false;
        }
    }

  
    public static boolean validateTableData(String projectId, 
      String datasetId, String tableName, List<Tkdata> expectedData) {
        try {
            BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService();
            String query = String.format("SELECT fchinf, numregs, valor FROM %s.%s.%s", 
                        projectId, datasetId, tableName);
            QueryJobConfiguration queryConfig = QueryJobConfiguration.newBuilder(query).build();

           
            TableResult result = bigquery.query(queryConfig);
            long rowCount = result.getTotalRows();

            if (rowCount == 0) {
                LOG.error("No hay datos en la tabla {}.{}", datasetId, tableName);
                return false;
            }

           
            if (expectedData != null && rowCount != expectedData.size()) {
                LOG.warn("Error en conteo de datos {}, found {}", expectedData.size(), rowCount);
                return false;
            }

            
            LOG.info("Sample data from {}.{} (first 5 rows):", datasetId, tableName);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            int maxRowsToLog = 5;
            int rowIndex = 0;
            for (FieldValueList row : result.iterateAll()) {
                if (rowIndex >= maxRowsToLog) break;
                String fchinf = row.get("fchinf").getStringValue(); 
                long numregs = row.get("numregs").getLongValue();
                String valor = row.get("valor").getStringValue();
                LOG.info("Row {}: fchinf={}, numregs={}, valor={}", rowIndex + 1, fchinf, numregs, valor);
                rowIndex++;
            }

            LOG.info("Validacion exitosa: {} rows found in table {}.{}", 
          rowCount, datasetId, tableName);
            return true;
        } catch (Exception e) {
            LOG.error("Error validando datos en la tabla {}.{}", datasetId, tableName, e);
            return false;
        }
    }

    public static void main(String[] args) {
     
        if (!existFile(BUCKET, NAME_FILE_ORIGINAL)) {
            LOG.error("Archivo{} no existe en el buckett {}.", NAME_FILE_ORIGINAL, BUCKET);
            return;
        }

        
        if (!maxSize(BUCKET, NAME_FILE_ORIGINAL, MAX_SIZE_BYTES)) {
            LOG.error("El archivo {} excede el tamaño: 300MB.", NAME_FILE_ORIGINAL);
            return;
        }

        
        List<Tkdata> listaTkdata = getListTkdata(BUCKET, NAME_FILE_ORIGINAL);
        if (listaTkdata.isEmpty()) {
            LOG.error("No hay datos para leer {}. ", NAME_FILE_ORIGINAL);
            return;
        }

        StringBuilder sb = convertToStringBuilder(listaTkdata);

        
        if (creaNuevoFileCSV(BUCKET, NAME_NEW_FILE, sb.toString())) {
            LOG.info("Archivo {} cargado al bucket {}", NAME_NEW_FILE, BUCKET);
            LOG.info("Carga de datos {} a la tabla: {}.{}", NAME_NEW_FILE, DATASET, TABLE_NAME);
            if (loadCSVToTableBigQuery(PROJECT, DATASET, BUCKET, NAME_NEW_FILE, TABLE_NAME)) {
                if (validateTableData(PROJECT, DATASET, TABLE_NAME, listaTkdata)) {
                    LOG.info("Validacion correcta {}.{}", DATASET, TABLE_NAME);
                } else {
                    LOG.error("Validacion fallida {}.{}", DATASET, TABLE_NAME);
                }
            } else {
                LOG.error("Fallo al cargar los datos a la tabla de BigQuery  {}.{}", DATASET, TABLE_NAME);
            }
        } else {
            LOG.error("Fallo al cargar el archivo {} al bucket {}", NAME_NEW_FILE, BUCKET);
        }
    }
}

Es importante hacer notar que los tipos de datos en el archivo deben concordar a los datos de la tabla. En caso contrario, no cargarán.

También es importante tener estas dependencias en el pom.xml

<dependencies>
    <dependency>
        <groupId>com.google.cloud</groupId>
        <artifactId>google-cloud-storage</artifactId>
        <version>2.44.0</version>
    </dependency>
    <dependency>
        <groupId>com.google.cloud</groupId>
        <artifactId>google-cloud-bigquery</artifactId>
        <version>2.44.0</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>2.0.13</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>2.0.13</version>
    </dependency>
</dependencies>

De preferncia usar Eclipse IDE para generar un .jar o ejecutarlo directamente.

Si la ejecución es correcta podremos ver los datos cargados en la tabla. Continuaremos más sobre BigQuery en próximas entregas.

Enlaces:

https://cloud.google.com/bigquery/docs/loading-data-cloud-storage-csv

viernes, 20 de junio de 2025

n8n: una herramienta para crear agentes de automatización

n8n es una herramienta (o plataforma) de automatización de flujos de trabajo de código abierto que permite conectar aplicaciones y servicios para automatizar tareas repetitivas sin necesidad de programar.

Permite la automatización de flujos de trabajo centrada en integrar aplicaciones y servicios (como APIs, bases de datos, o herramientas como Slack) mediante una interfaz visual.

Con n8n se trabaja de forma visual mediante flujos (workflows).

Es ideal para automatizar procesos de negocio, notificaciones o flujos de datos entre aplicaciones sin necesidad de programar.

n8n automatiza flujos de trabajo entre apps y es más accesible para no programadores.

Ejemplos de uso:

  • Detectar un nuevo issue en GitHub y crear una tarea en Trello. 
  • Conectar GitHub con Slack para notificar commits.

Caso de uso: Automatizar notificaciones de nuevos commits en GitHub.

  • Nodo 1: GitHub Trigger Configura un nodo que detecta nuevos commits en un repositorio de GitHub. 
  • Nodo 2: Discord o Slack Conecta un nodo que envía un mensaje a un canal de Discord o Slack con detalles del commit (autor, mensaje, enlace). 
  • Flujo: GitHub → Detecta nuevo commit → Envía notificación a Discord/Slack. Esto ayuda a mantener al equipo informado sin intervención manual, y se configura visualmente en n8n.

Enlaces:

https://n8n.io/


domingo, 15 de junio de 2025

Composer, un administrador de dependencias para PHP

Composer es un administrador de dependencias para PHP que rastrea las dependencias locales de sus proyectos y bibliotecas. Para quienes usan Java, Composer vendría siendo algo como Maven o como CPAN para los programadores Perl.

Se usa en los principales frameworks web modernos para PHP (como Laravel, Symfony, etc.). Para descargarlo debemos ir a este sitio: https://getcomposer.org/download/

También existe una imagen Docker de Composer:

$ docker pull composer/composer
$ docker run --rm -it -v "$(pwd):/app" composer/composer install

Pero, antes de hacer nada, ¿Para qué sirve Composer?

Como desarrolladores PHP, nos ayuda a:

  • Resolver las dependencias para paquetes PHP.
  • Cargar automáticamente paquetes de PHP.
  • Mantener todos los paquetes actualizados.

Una vez instalado, verificamos:

$ composer --version

Si todo va bien, mostrará versión instalada.

A continuación una pequeña lista de comandos más usados de Composer

Comandos más usados de Composer

Obtener ayuda:

$ composer --help

Listar comandos disponibles:

$ composer list

Obtener información de Composer:

$ composer about

Inicializa un nuevo proyecto con un archivo composer.json :

$ composer init

Instala un paquete y lo añade a composer.json :

$ composer require [paquete]

Ejemplo:

$ composer require monolog/monolog

Instala todas las dependencias listadas en composer.json :

$ composer install

Actualiza las dependencias a sus versiones más recientes compatibles :

$ composer update

Desinstala un paquete y lo elimina de composer.json :

$ composer remove [paquete]

Ejemplo:

$ composer remove monolog/monolog

Regenera el archivo de autoloading (vendor/autoload.php):

$ composer dump-autoload

Diagnosticar el sistema para identificar errores comunes:

$ composer diagnose

También es usado para crear proyectos. Veamos un ejemplo de como crear un proyecto Symfony:

$ composer create-project symfony/skeleton:"7.3.x-dev" miproyecto

Esto nos creará un proyecto Symfony, para ejecutar la aplicación debemos ejecutar este comando:

$ symfony server:start

Abrimos el navegador en la dirección: http://localhost:8000/

Continuaremos con esta serie sobre PHP y sus herramientas.

Enlaces:

https://getcomposer.org
https://www.codementor.io/@jadjoubran/php-tutorial-getting-started-with-composer-8sbn6fb6t

sábado, 14 de junio de 2025

Un framework web ligero para PHP: CodeIgniter

Según el sitio oficial:

CodeIgniter es un potente framework PHP de tamaño muy reducido, creado para desarrolladores que necesitan un conjunto de herramientas simple y elegante para crear aplicaciones web con todas las funciones.

Características:

  • Ligero: footprint pequeño (~2MB). 
  • MVC: soporta el patrón Modelo-Vista-Controlador. 
  • Fácil configuración: mínima configuración inicial. 
  • Rendimiento: optimizado para alta velocidad. 
  • Seguridad: incluye herramientas para proteger contra CSRF, XSS, y validación de datos. 
  •  Documentación clara: guías detalladas y comunidad activa. 
  •  Flexibilidad: no impone estructuras rígidas, compatible con PHP 7.4+.

Como ya es habitual en este blog crearemos un sencillo ejemplo usando ahora este micro framework.

Usaremos Composer para crear el proyecto.

Creando un proyecto con CodeIgniter(y Composer)

Creamos el proyecto:

$ composer create-project codeigniter4/appstarter hola-mundo

Nos ubicamos en el directorio creado:

$ cd hola-mundo

Copiamos el archivo env a .env

$ cp env .env

Editamos el archivo .env y descomentamos y ajustamos la variable app.baseURL para que quede de la siguiente manera:

app.baseURL = 'http://localhost:8080'

Ejecutamos la aplicación:

$ php spark serve

Abrimos el navegador en la dirección:

http://localhost:8080

¡Hemos creado nuestra primera aplicación usando CodeIgniter y Compose!

En algunos casos deberás editar el archivo php.ini y descomentar extension=intl (quitando el ;) :

extension=intl

Ejecutar este comando e iniciar de nuevo el server:

$ compose install
$ php spark serve

Enlaces:

https://www.codeigniter.com/

viernes, 13 de junio de 2025

Conociendo Laravel: un framework para PHP

Laravel es un framework de PHP para desarrollar aplicaciones web, conocido por su sintaxis elegante, herramientas integradas y enfoque en la simplicidad y robustez. Facilita tareas como autenticación, enrutamiento, sesiones y más.

Si ya contamos con PHP y Composer instalados podemos crear un proyecto de la siguiente forma:

$ composer global require laravel/installer

Creamos un directorio y nos ubicamos en este:

$ mkdir proyecto
$ cd proyecto

Verificamos instalación:

$ laravel  --version

Creamos el proyecto:

$ laravel  new generador

Esto nos mostrará un prompt, elegiremos todo por default. Si todo va bien, se generará nuestro proyecto.

Nos ubicamos en el directorio creado (generador):

$ cd generador

Instalamos las dependencias y ejecutamos la aplicación:

$ npm install && npm run build
$ composer run dev

Si todo va bien, abriremos el navegador en la dirección:

http://localhost:5173/

¡Hemos creado nuestra primera aplicación con Laravel!

Enlaces:

https://laravel.com/

Slim framework: un micro framework para PHP

Slim es un micro framework PHP que le ayuda a escribir rápidamente aplicaciones web y API simples pero potentes.

Requisitos:

  • Si no se tiene PHP instalado, instalarlo. 
  • Si no se tiene Composer, instalarlo.

PHP:

https://www.php.net/downloads.php

Composer:

https://getcomposer.org/download/

Nota: Durante la instalación se te pedirá un proxy. Si no lo tienes, omite el paso y continua. También requerirás reiniciar tu máquina.

Instalando Slim Framework

Comprobamos la instalación de PHP y Composer:

$ php --version

$ composer --version

Creamos un directorio, nos posicionamos sobre este y generamos el proyecto:

$ mkdir proyecto-slim
$ cd proyecto-slim
$ composer create-project slim/slim-skeleton generator

Una vez generado, nos ubicamos en el directorio creado:

$ cd generator

index.php

Ejecutamos el proyecto:

$ php -S localhost:8081 -t public

¡Hemos creado nuestro primer proyecto con Slim Framework!

Hay otra forma de instalar y crear un proyecto. Debemos crear un directorio y ubicarnos en el.

$ mkdir proyecto
$ cd proyecto

Instalamos lo necesario para trabajar con Slim Framework:

$ composer require slim/slim
$ composer require slim/psr7

Se creará un directorio vendor y dos archivos (composer.json y composer.lock).

Verificamos el archivo JSON (vendor/composer.json):

{
    "require": {
        "slim/slim": "^4.0",
        "slim/psr7": "^1.0"
    }
}

Creamos un archivo index.php

<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;

require __DIR__ . '/vendor/autoload.php';

$app = AppFactory::create();

$app->get('/', function (Request $request, Response $response, array $args) {
    $response->getBody()->write("¡Hola, Slim Framework!");
    return $response;
});

$app->run();

Ejecutamos la aplicación:

$ php -S localhost:8081

Tendremos ejecutandose nuestra aplicación en la dirección:

http://localhost:8081/

Algunas impresiones:

  • Para ser un "micro" framework se genera todo un proyecto con muchos directorios y archivos "innecesarios". 
  • Habrá que realizar un escudriñamiento más a fondo para ver cuales son sus puntos fuertes y de donde flaquea.

Enlaces:

https://www.slimframework.com/
https://getcomposer.org/download/

Vibe Coding (la programación vía IA): ¿el futuro de la programación?

Vibe Coding es un nuevo paradigma de programación, una nueva forma de crear código. Es un enfoque emergente en el desarrollo de sof...

Etiquetas

Archivo del blog