[{"content":"Procedimientos almacenados y Packages en Oracle PL/SQL (Procedural Language/SQL) es el lenguaje procedural de Oracle para escribir lógica de negocio dentro de la base de datos: procedimientos, funciones, cursores, manejo de excepciones y packages. En este post vamos a repasar, de lo básico a lo completo:\nBloques PL/SQL y variables Procedimientos almacenados (parámetros IN, OUT, IN OUT) Funciones almacenadas Cursores explícitos Manejo de excepciones Packages (especificación y cuerpo) Buenas prácticas 1. El bloque PL/SQL Todo código PL/SQL se organiza en bloques con esta estructura:\n1 2 3 4 5 6 7 DECLARE -- declaración de variables, constantes, cursores BEGIN -- lógica del programa EXCEPTION -- manejo de errores END; Un bloque anónimo se ejecuta una sola vez y no se guarda en la base de datos:\n1 2 3 4 5 6 7 8 9 10 11 DECLARE v_nombre VARCHAR2(100); BEGIN SELECT nombre INTO v_nombre FROM clientes WHERE id = 1; DBMS_OUTPUT.PUT_LINE(\u0026#39;Cliente: \u0026#39; || v_nombre); END; / DBMS_OUTPUT.PUT_LINE imprime en consola (útil para debug en SQL*Plus/SQL Developer). La barra / al final le indica al cliente SQL que ejecute el bloque completo. Cuando esa lógica se quiere reutilizar, se guarda como procedimiento, función o dentro de un package.\n2. Variables y tipos de datos Además de los tipos escalares (VARCHAR2, NUMBER, DATE, BOOLEAN), PL/SQL ofrece dos atributos muy usados para evitar desincronizar tipos con la tabla:\n%TYPE: toma el tipo de dato de una columna (o variable) existente. %ROWTYPE: toma la estructura completa de una fila de una tabla o cursor. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 DECLARE v_id_cliente clientes.id%TYPE; v_cliente clientes%ROWTYPE; BEGIN v_id_cliente := 1; SELECT * INTO v_cliente FROM clientes WHERE id = v_id_cliente; DBMS_OUTPUT.PUT_LINE(v_cliente.nombre); END; / Usar %TYPE y %ROWTYPE evita que tu código se rompa si mañana cambia el tipo o el largo de una columna: la variable siempre queda sincronizada con la tabla.\n3. Procedimientos almacenados Un procedimiento es un bloque PL/SQL con nombre, guardado en la base de datos, que puede recibir parámetros pero no retorna un valor con RETURN (aunque sí puede devolver datos a través de parámetros OUT).\nSintaxis básica 1 2 3 4 5 6 7 8 9 10 CREATE OR REPLACE PROCEDURE nombre_procedimiento ( parametro1 IN tipo, parametro2 OUT tipo ) IS -- declaraciones BEGIN -- lógica END nombre_procedimiento; / IN: el valor entra al procedimiento (por defecto, si no se indica nada es IN). OUT: el procedimiento devuelve un valor a través de ese parámetro. IN OUT: el parámetro entra con un valor y el procedimiento lo puede modificar. Ejemplo con parámetro OUT 1 2 3 4 5 6 7 8 9 10 11 12 CREATE OR REPLACE PROCEDURE obtener_total_cliente ( p_id_cliente IN clientes.id%TYPE, p_total OUT NUMBER ) IS BEGIN SELECT NVL(SUM(total), 0) INTO p_total FROM ordenes WHERE cliente_id = p_id_cliente; END obtener_total_cliente; / Ejecutarlo:\n1 2 3 4 5 6 7 DECLARE v_total NUMBER; BEGIN obtener_total_cliente(p_id_cliente =\u0026gt; 1, p_total =\u0026gt; v_total); DBMS_OUTPUT.PUT_LINE(\u0026#39;Total gastado: \u0026#39; || v_total); END; / Ejemplo con IN OUT 1 2 3 4 5 6 7 8 9 CREATE OR REPLACE PROCEDURE aplicar_descuento ( p_total IN OUT NUMBER, p_porcentaje IN NUMBER ) IS BEGIN p_total := p_total - (p_total * p_porcentaje / 100); END aplicar_descuento; / 1 2 3 4 5 6 7 DECLARE v_total NUMBER := 200; BEGIN aplicar_descuento(v_total, 10); DBMS_OUTPUT.PUT_LINE(\u0026#39;Total con descuento: \u0026#39; || v_total); -- 180 END; / Insertar/actualizar datos desde un procedimiento 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 CREATE OR REPLACE PROCEDURE registrar_orden ( p_cliente_id IN ordenes.cliente_id%TYPE, p_total IN ordenes.total%TYPE ) IS BEGIN INSERT INTO ordenes (id, cliente_id, fecha, total) VALUES (ordenes_seq.NEXTVAL, p_cliente_id, SYSDATE, p_total); COMMIT; EXCEPTION WHEN OTHERS THEN ROLLBACK; RAISE; END registrar_orden; / El COMMIT dentro de un procedimiento es una decisión de diseño: en procesos más grandes, muchas veces conviene dejar el COMMIT a cargo de quien invoca el procedimiento, para no comprometer transacciones que abarcan varias operaciones.\n4. Funciones almacenadas Una función es igual a un procedimiento, pero siempre retorna un valor con RETURN y por lo general se usa dentro de expresiones SQL.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 CREATE OR REPLACE FUNCTION calcular_total_cliente ( p_id_cliente IN clientes.id%TYPE ) RETURN NUMBER IS v_total NUMBER; BEGIN SELECT NVL(SUM(total), 0) INTO v_total FROM ordenes WHERE cliente_id = p_id_cliente; RETURN v_total; END calcular_total_cliente; / Se puede usar directo en una consulta:\n1 2 3 4 SELECT c.nombre, calcular_total_cliente(c.id) AS total_gastado FROM clientes AS c; Regla práctica: si necesitas un valor único como resultado y quieres usarlo dentro de un SELECT, usa una función. Si necesitas ejecutar una acción (insertar, actualizar, encadenar varios pasos) o devolver varios valores, usa un procedimiento.\n5. Cursores explícitos Un cursor permite recorrer fila por fila el resultado de una consulta, cuando un simple SELECT INTO no alcanza (porque esperas más de una fila).\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 DECLARE CURSOR c_ordenes IS SELECT cliente_id, total FROM ordenes WHERE total \u0026gt; 100; v_cliente_id ordenes.cliente_id%TYPE; v_total ordenes.total%TYPE; BEGIN OPEN c_ordenes; LOOP FETCH c_ordenes INTO v_cliente_id, v_total; EXIT WHEN c_ordenes%NOTFOUND; DBMS_OUTPUT.PUT_LINE(v_cliente_id || \u0026#39; - \u0026#39; || v_total); END LOOP; CLOSE c_ordenes; END; / %NOTFOUND: TRUE cuando el último FETCH no trajo fila. %FOUND: lo contrario. %ROWCOUNT: cantidad de filas leídas hasta el momento. Cursor FOR loop (forma recomendada) Cuando no necesitas control fino sobre OPEN/FETCH/CLOSE, el cursor FOR loop es más corto y Oracle se encarga de abrir y cerrar el cursor automáticamente:\n1 2 3 4 5 6 7 8 9 10 11 BEGIN FOR r_orden IN ( SELECT cliente_id, total FROM ordenes WHERE total \u0026gt; 100 ) LOOP DBMS_OUTPUT.PUT_LINE(r_orden.cliente_id || \u0026#39; - \u0026#39; || r_orden.total); END LOOP; END; / Cursor con parámetros 1 2 3 4 5 6 7 8 9 10 11 DECLARE CURSOR c_ordenes_cliente (p_cliente_id NUMBER) IS SELECT total FROM ordenes WHERE cliente_id = p_cliente_id; BEGIN FOR r IN c_ordenes_cliente(1) LOOP DBMS_OUTPUT.PUT_LINE(r.total); END LOOP; END; / 6. Estructuras de control 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 -- IF / ELSIF / ELSE IF v_total \u0026gt; 1000 THEN v_categoria := \u0026#39;VIP\u0026#39;; ELSIF v_total \u0026gt; 100 THEN v_categoria := \u0026#39;Regular\u0026#39;; ELSE v_categoria := \u0026#39;Nuevo\u0026#39;; END IF; -- LOOP básico LOOP v_contador := v_contador + 1; EXIT WHEN v_contador \u0026gt; 10; END LOOP; -- FOR loop numérico FOR i IN 1..10 LOOP DBMS_OUTPUT.PUT_LINE(i); END LOOP; -- WHILE WHILE v_contador \u0026lt;= 10 LOOP v_contador := v_contador + 1; END LOOP; 7. Manejo de excepciones PL/SQL distingue entre excepciones predefinidas (NO_DATA_FOUND, TOO_MANY_ROWS, DUP_VAL_ON_INDEX, ZERO_DIVIDE, entre otras) y excepciones definidas por el usuario.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 DECLARE v_total NUMBER; e_total_negativo EXCEPTION; BEGIN SELECT total INTO v_total FROM ordenes WHERE id = 999; -- id que no existe IF v_total \u0026lt; 0 THEN RAISE e_total_negativo; END IF; EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE(\u0026#39;No existe la orden.\u0026#39;); WHEN e_total_negativo THEN DBMS_OUTPUT.PUT_LINE(\u0026#39;El total no puede ser negativo.\u0026#39;); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(\u0026#39;Error inesperado: \u0026#39; || SQLERRM); RAISE; END; / WHEN OTHERS captura cualquier excepción no controlada explícitamente. Úsalo casi siempre junto a RAISE (para no \u0026ldquo;tragarte\u0026rdquo; el error) o registrando el error en una tabla de logs. SQLERRM y SQLCODE dan el mensaje y código del error actual. RAISE_APPLICATION_ERROR(-20001, 'mensaje') permite lanzar errores de aplicación personalizados (códigos entre -20000 y -20999) que llegan al cliente que invoca el procedimiento. 1 2 3 IF v_total \u0026lt; 0 THEN RAISE_APPLICATION_ERROR(-20001, \u0026#39;El total no puede ser negativo\u0026#39;); END IF; 8. Packages: agrupando procedimientos y funciones Un package agrupa procedimientos, funciones, variables, cursores y tipos relacionados en una sola unidad lógica. Se compone de dos partes:\nPackage specification (PACKAGE): la interfaz pública, lo que otros pueden ver y llamar. Package body (PACKAGE BODY): la implementación, incluyendo elementos privados que no aparecen en la especificación. Ventajas de usar packages Encapsulamiento: puedes tener funciones/variables privadas en el body que no son visibles desde afuera. Organización: agrupa lógica relacionada (todo lo de \u0026ldquo;clientes\u0026rdquo; en un mismo package) en vez de decenas de procedimientos sueltos. Overloading: puedes tener varias versiones de un mismo procedimiento/función con distinta firma de parámetros. Estado en memoria de sesión: variables declaradas en el package mantienen su valor durante toda la sesión, útil para caché o configuración. Mejor rendimiento: Oracle carga el package completo en memoria la primera vez que se usa, y solo se recompila el body si cambia (sin invalidar a quienes dependen de la especificación). Especificación del package 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 CREATE OR REPLACE PACKAGE pkg_clientes IS -- Constante pública c_categoria_vip CONSTANT VARCHAR2(10) := \u0026#39;VIP\u0026#39;; -- Función pública FUNCTION calcular_total_cliente ( p_id_cliente IN clientes.id%TYPE ) RETURN NUMBER; -- Procedimiento público PROCEDURE registrar_orden ( p_cliente_id IN ordenes.cliente_id%TYPE, p_total IN ordenes.total%TYPE ); -- Overload: dos procedimientos con el mismo nombre, distinta firma PROCEDURE actualizar_categoria ( p_cliente_id IN clientes.id%TYPE ); PROCEDURE actualizar_categoria ( p_cliente_id IN clientes.id%TYPE, p_categoria IN VARCHAR2 ); END pkg_clientes; / Cuerpo del package 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 CREATE OR REPLACE PACKAGE BODY pkg_clientes IS -- Función PRIVADA: no está en la especificación, no se puede llamar desde afuera FUNCTION obtener_categoria_por_total ( p_total IN NUMBER ) RETURN VARCHAR2 IS BEGIN IF p_total \u0026gt; 1000 THEN RETURN c_categoria_vip; ELSIF p_total \u0026gt; 100 THEN RETURN \u0026#39;Regular\u0026#39;; ELSE RETURN \u0026#39;Nuevo\u0026#39;; END IF; END obtener_categoria_por_total; -- Implementación de la función pública FUNCTION calcular_total_cliente ( p_id_cliente IN clientes.id%TYPE ) RETURN NUMBER IS v_total NUMBER; BEGIN SELECT NVL(SUM(total), 0) INTO v_total FROM ordenes WHERE cliente_id = p_id_cliente; RETURN v_total; END calcular_total_cliente; PROCEDURE registrar_orden ( p_cliente_id IN ordenes.cliente_id%TYPE, p_total IN ordenes.total%TYPE ) IS BEGIN INSERT INTO ordenes (id, cliente_id, fecha, total) VALUES (ordenes_seq.NEXTVAL, p_cliente_id, SYSDATE, p_total); COMMIT; EXCEPTION WHEN OTHERS THEN ROLLBACK; RAISE; END registrar_orden; -- Overload 1: calcula la categoría automáticamente PROCEDURE actualizar_categoria ( p_cliente_id IN clientes.id%TYPE ) IS v_total NUMBER; v_categoria VARCHAR2(10); BEGIN v_total := calcular_total_cliente(p_cliente_id); v_categoria := obtener_categoria_por_total(v_total); UPDATE clientes SET categoria = v_categoria WHERE id = p_cliente_id; COMMIT; END actualizar_categoria; -- Overload 2: la categoría se pasa manualmente PROCEDURE actualizar_categoria ( p_cliente_id IN clientes.id%TYPE, p_categoria IN VARCHAR2 ) IS BEGIN UPDATE clientes SET categoria = p_categoria WHERE id = p_cliente_id; COMMIT; END actualizar_categoria; END pkg_clientes; / Usando el package 1 2 3 4 5 6 7 BEGIN pkg_clientes.registrar_orden(p_cliente_id =\u0026gt; 1, p_total =\u0026gt; 350); pkg_clientes.actualizar_categoria(p_cliente_id =\u0026gt; 1); END; / SELECT pkg_clientes.calcular_total_cliente(1) FROM dual; obtener_categoria_por_total no se puede llamar desde fuera del package, por ejemplo pkg_clientes.obtener_categoria_por_total(500) fallaría, ya que no está declarada en la especificación. Ambos actualizar_categoria conviven porque Oracle distingue el overload por el número y tipo de parámetros. Inicialización del package Un package puede tener un bloque de inicialización, que se ejecuta una sola vez por sesión, la primera vez que se referencia algo del package:\n1 2 3 4 5 6 7 8 CREATE OR REPLACE PACKAGE BODY pkg_clientes IS -- ... funciones y procedimientos ... BEGIN DBMS_OUTPUT.PUT_LINE(\u0026#39;pkg_clientes inicializado para esta sesión\u0026#39;); END pkg_clientes; / Esto es útil para cargar valores de configuración en variables globales del package al inicio de la sesión.\n9. Buenas prácticas al escribir PL/SQL Agrupa en packages procedimientos/funciones relacionados, en vez de dejarlos sueltos en el esquema. Usa %TYPE y %ROWTYPE para no desincronizar tipos de datos con las tablas. Evita SELECT * dentro de procedimientos, declara explícitamente las columnas que necesitas. Maneja siempre las excepciones: como mínimo un WHEN OTHERS con RAISE o log del error, nunca lo dejes \u0026ldquo;tragado\u0026rdquo; en silencio. No mezcles COMMIT/ROLLBACK en procedimientos que son parte de una transacción más grande, deja esa responsabilidad a quien orquesta el proceso completo cuando sea posible. Nombra los parámetros con prefijo (p_) y las variables locales con otro (v_), para diferenciarlos rápido en procedimientos largos. Usa cursores FOR loop en vez de OPEN/FETCH/CLOSE manual cuando no necesites control fino, es menos propenso a errores (olvidar cerrar el cursor, loops infinitos). Resumen Objeto Retorna valor Se puede usar en SELECT Encapsula lógica privada Procedimiento No (solo vía OUT/IN OUT) No No Función Sí (RETURN) Sí No Package Agrupa ambos Sí (sus funciones) Sí, en el body Con procedimientos, funciones y packages bien organizados, la lógica de negocio queda centralizada en la base de datos, es reutilizable desde cualquier cliente (aplicación, reportes, otros procedimientos) y más fácil de mantener en el tiempo.\n","date":"2026-08-01T12:00:00-04:00","permalink":"https://blog.trxsalo.xyz/p/procedimientos-almacenados-oracle/","title":"Procedimientos almacenados y Packages en Oracle - Guía completa de PL/SQL"},{"content":"Buenas prácticas en SQL Escribir SQL no es solo lograr que la consulta devuelva el resultado esperado, sino escribirla de forma que cualquier otra persona (o tú mismo en seis meses) pueda leerla y entenderla rápidamente. En este post vamos a repasar recomendaciones prácticas sobre:\nCódigo limpio y tabulado Uso correcto de alias Funciones de ventana (window functions) Tipos de JOIN y cuándo usar cada uno 1. Código limpio y tabulado Una consulta SQL mal formateada es difícil de leer, aunque sea correcta. Algunas reglas simples ayudan mucho:\nUna cláusula por línea (SELECT, FROM, WHERE, GROUP BY, ORDER BY, etc.) Los campos del SELECT, uno por línea y alineados con tabulación Palabras clave en mayúsculas para diferenciarlas de nombres de columnas/tablas Indentar las condiciones que dependen de otra cláusula Mal formateado:\n1 select c.id, c.nombre, o.fecha, o.total from clientes c join ordenes o on c.id = o.cliente_id where o.total \u0026gt; 100 order by o.fecha desc Bien formateado:\n1 2 3 4 5 6 7 8 9 10 SELECT c.id, c.nombre, o.fecha, o.total FROM clientes AS c JOIN ordenes AS o ON c.id = o.cliente_id WHERE o.total \u0026gt; 100 ORDER BY o.fecha DESC; Con consultas cortas la diferencia no se nota tanto, pero en consultas de 100+ líneas con varios JOIN y subconsultas, un buen formato es la diferencia entre depurar en minutos o en horas.\nRecomendaciones generales Usa un largo de línea razonable, evita líneas de 200 caracteres. Agrupa condiciones relacionadas con paréntesis y una condición por línea cuando el WHERE crece. Usa CTEs (WITH) en lugar de subconsultas anidadas cuando la lógica se vuelve compleja, son mucho más legibles. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 WITH ventas_por_cliente AS ( SELECT cliente_id, SUM(total) AS total_gastado FROM ordenes GROUP BY cliente_id ) SELECT c.nombre, v.total_gastado FROM clientes AS c JOIN ventas_por_cliente AS v ON v.cliente_id = c.id WHERE v.total_gastado \u0026gt; 1000 ORDER BY v.total_gastado DESC; 2. Uso correcto de alias Los alias (AS) sirven para acortar nombres de tablas y para nombrar columnas calculadas. Bien usados, hacen la consulta mucho más legible; mal usados, la vuelven confusa.\nRecomendaciones Usa siempre AS para declarar el alias, aunque sea opcional en muchos motores. Deja explícita la intención. Alias cortos pero descriptivos para tablas: prefiere iniciales con sentido (c para clientes, o para ordenes) en vez de letras aleatorias (a, b, x). No abuses de alias de una sola letra en consultas con muchas tablas, ahí es mejor usar abreviaturas de 2-3 letras (cli, ord, prod). Alias en columnas calculadas siempre, para que el resultado tenga un nombre claro. 1 2 3 4 5 6 7 8 SELECT c.nombre AS cliente, COUNT(o.id) AS cantidad_ordenes, SUM(o.total) AS total_gastado FROM clientes AS c JOIN ordenes AS o ON o.cliente_id = c.id GROUP BY c.nombre; Sin alias en las columnas calculadas, el resultado se vería con nombres como COUNT(o.id) o SUM(o.total), poco amigables para quien consuma el reporte.\nEvita reutilizar el mismo alias para cosas distintas dentro de la misma consulta (por ejemplo usar t para total en un SELECT y para tabla en otro FROM). Es una fuente común de confusión al leer consultas largas.\n3. Funciones de ventana (Window Functions) Las funciones de ventana permiten hacer cálculos sobre un conjunto de filas relacionadas con la fila actual, sin colapsar el resultado como lo hace GROUP BY. Se definen con OVER().\nSintaxis general 1 2 3 4 funcion() OVER ( PARTITION BY columna ORDER BY otra_columna ) PARTITION BY: agrupa las filas en \u0026ldquo;particiones\u0026rdquo; (similar a GROUP BY, pero sin colapsar filas). ORDER BY: define el orden dentro de cada partición, necesario para funciones como ROW_NUMBER o los acumulados. Casos de uso comunes Ranking de ventas por cliente\n1 2 3 4 5 6 7 8 9 SELECT cliente_id, fecha, total, ROW_NUMBER() OVER ( PARTITION BY cliente_id ORDER BY total DESC ) AS ranking_orden FROM ordenes; ROW_NUMBER(): numera filas de forma única, útil para obtener el \u0026ldquo;top N por grupo\u0026rdquo; o eliminar duplicados. RANK() / DENSE_RANK(): similares a ROW_NUMBER, pero permiten empates. RANK deja huecos en la numeración tras un empate, DENSE_RANK no. Total acumulado (running total)\n1 2 3 4 5 6 7 8 9 SELECT fecha, total, SUM(total) OVER ( ORDER BY fecha ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS total_acumulado FROM ordenes ORDER BY fecha; Útil para reportes financieros, saldos acumulados o métricas de crecimiento día a día.\nComparar con la fila anterior/siguiente\n1 2 3 4 5 6 7 8 9 SELECT cliente_id, fecha, total, LAG(total) OVER ( PARTITION BY cliente_id ORDER BY fecha ) AS total_orden_anterior FROM ordenes; LAG(): trae el valor de la fila anterior dentro de la partición. LEAD(): trae el valor de la fila siguiente. Ideal para calcular variaciones entre periodos (por ejemplo, cuánto aumentó o disminuyó una compra respecto a la anterior).\nRegla práctica: si necesitas un cálculo agregado (suma, conteo, ranking, promedio) pero sin perder el detalle de cada fila, piensa en una función de ventana antes que en un GROUP BY con subconsultas.\n4. Tipos de JOIN y cuándo usar cada uno Elegir el JOIN correcto es una de las decisiones que más impacta tanto el resultado como el rendimiento de una consulta.\nINNER JOIN Devuelve solo las filas que tienen coincidencia en ambas tablas.\n1 2 3 4 5 6 SELECT c.nombre, o.total FROM clientes AS c INNER JOIN ordenes AS o ON o.cliente_id = c.id; Cuándo usarlo: cuando solo te interesan los registros que existen en ambas tablas. Por ejemplo, clientes que sí tienen al menos una orden.\nLEFT JOIN (o LEFT OUTER JOIN) Devuelve todas las filas de la tabla izquierda, y las coincidencias de la derecha si existen (si no, rellena con NULL).\n1 2 3 4 5 6 SELECT c.nombre, o.total FROM clientes AS c LEFT JOIN ordenes AS o ON o.cliente_id = c.id; Cuándo usarlo: cuando necesitas conservar todos los registros de la tabla principal, tengan o no relación. Por ejemplo, listar todos los clientes, incluso los que nunca compraron (para detectar clientes inactivos, o.total vendría NULL).\n1 2 3 4 5 6 -- Clientes sin ninguna orden SELECT c.nombre FROM clientes AS c LEFT JOIN ordenes AS o ON o.cliente_id = c.id WHERE o.id IS NULL; RIGHT JOIN (o RIGHT OUTER JOIN) Es el espejo del LEFT JOIN: conserva todas las filas de la tabla derecha.\n1 2 3 4 5 6 SELECT c.nombre, o.total FROM clientes AS c RIGHT JOIN ordenes AS o ON o.cliente_id = c.id; Cuándo usarlo: en la práctica es poco común, porque cualquier RIGHT JOIN se puede reescribir como un LEFT JOIN invirtiendo el orden de las tablas, que suele ser más legible. Aun así, es útil conocerlo para leer consultas de terceros.\nFULL JOIN (o FULL OUTER JOIN) Devuelve todas las filas de ambas tablas, con NULL donde no hay coincidencia.\n1 2 3 4 5 6 SELECT c.nombre, o.total FROM clientes AS c FULL JOIN ordenes AS o ON o.cliente_id = c.id; Cuándo usarlo: cuando necesitas ver el panorama completo de dos tablas, incluyendo lo que no coincide en ninguna de las dos direcciones. Por ejemplo, auditar diferencias entre dos sistemas (clientes que están en un sistema pero no en otro y viceversa).\nCROSS JOIN Genera el producto cartesiano: cada fila de una tabla combinada con cada fila de la otra.\n1 2 3 4 5 SELECT t.talla, col.color FROM tallas AS t CROSS JOIN colores AS col; Cuándo usarlo: para generar combinaciones, por ejemplo todas las combinaciones posibles de talla y color de un producto antes de crear el inventario. Se debe usar con cuidado, ya que el número de filas resultante es filas_tabla1 x filas_tabla2 y puede crecer muy rápido.\nSELF JOIN No es un tipo de JOIN distinto, sino una tabla unida consigo misma, usando alias para diferenciarla.\n1 2 3 4 5 6 SELECT e.nombre AS empleado, j.nombre AS jefe FROM empleados AS e LEFT JOIN empleados AS j ON e.jefe_id = j.id; Cuándo usarlo: cuando una tabla tiene una relación jerárquica o de referencia hacia sí misma, como empleados y sus jefes, o categorías con subcategorías.\nResumen de recomendaciones Práctica Recomendación Formato Una cláusula y un campo por línea, palabras clave en mayúsculas Alias Usar siempre AS, alias cortos pero descriptivos, sin ambigüedad Window Functions Úsalas cuando necesites cálculos agregados sin perder el detalle por fila INNER JOIN Solo coincidencias en ambas tablas LEFT / RIGHT JOIN Conservar todos los registros de una tabla, coincidan o no FULL JOIN Ver todo de ambas tablas, incluyendo lo que no coincide CROSS JOIN Generar combinaciones, usar con cuidado por el volumen de filas SELF JOIN Relaciones jerárquicas dentro de la misma tabla Aplicar estas prácticas de forma consistente hace que tus consultas sean más fáciles de mantener, de depurar y de compartir con el resto del equipo.\n","date":"2026-08-01T10:00:00-04:00","permalink":"https://blog.trxsalo.xyz/p/buenas-practicas-sql/","title":"Buenas prácticas en SQL - Código limpio, alias, JOINs y funciones de ventana"},{"content":" Servidor Nodejs con Express Iniciando el proyecto express Puedes utilizar tu editor de código favorito, en mi caso estaré utizando Visual Studio Code Primero empezaremos creando la carpeta donde trabajaremos.\n1 2 mkdir express-server cd express-server Una vez dentro de la carpeta, iniciamos el proytecto Nodejs, lo cual creará el archivo de configuración package.json\n1 npm init Dependencias del proyecto Dependecia de desarrollo. Las dependencias de desarrollo, son las librerías que solo se utilizarán en el desarrollo de la aplicación, las cuales ya no se haran uso en producción.\n1 2 npm i typescript -D npm i nodemon -D Typescript En este proyecto utilizaremos Ts para desarrollar el servidor express.\nTypeScript es un lenguaje de programación fuertemente tipado que se basa en JavaScript y le brinda mejores herramientas a cualquier escala.\n\u0026ndash;Typescript Nodemon Nodemon nos falicitara al momento de desarrollar, ya que monitorea el proyecto por cualquier cambios, y reiniciará automáticamente el servidor.\nDepencias del proyecto La siguiente dependecias son lo que requerirá el proyecto para funcionar correctamente.\n1 2 3 npm i morgan npm i express npm i dotenv Express Espress es una librería que nos permite construir un servidor para servir una página, aplicación web o api, lo cual también se ha convertido en el estándar para desarrollo de aplicaciones backend con nodejs.\nExpress.js, a veces también llamado «Express», es un framework de backend Node.js minimalista, rápido y similar a Sinatra, que proporciona características y herramientas robustas para desarrollar aplicaciones de backend escalables.\n\u0026ndash;Kinsta\nMorgan\nNos mostrara las solicitudes y respuestas del servidor, las cuales podemos visualizar por consola.\nMorgan es un Middleware de nivel de solicitud HTTP. Es una gran herramienta que registra las requests junto con otra información dependiendo de su configuración y el ajuste preestablecido utilizado. Resulta muy útil durante la depuración y también si desea crear archivos de registro.\n\u0026ndash;Rudeus G Dotenv\nPara uso de variable de entorno.\n@Types\nNos perimite el uso de tipado en las dependecias que instalemos\n1 2 npm i @type/express -D npm i @types/dotenv -D Configurando Typescript Para poder empezar utiliziar typescript en el proyecto, debemos hacer unas modifcaciones primero.\nEn la raiz del proyecto ejecutaremos el siguiente comando.\nCrea el archivo de configuracion typescript para compilar.\n1 npx tsc --init Una vez ejecutado el comando, abrimos el archivo tsconfig.json para hacer algunas modificaciones.\nDentro del archivo buscaremos lo siguiente para descomentarlo y agregar la siguiente configuración:\nrootDirt : Indica donde estarán los archivos typescript\noutDirt : Indica donde se guardaran los arhivos compilados\n1 2 \u0026#34;rootDir\u0026#34;: \u0026#34;./src\u0026#34;, \u0026#34;outDir\u0026#34;: \u0026#34;./dist\u0026#34;, Lo siguiente es opcional\ntarget : Indica a versión de javascript compilara los archivos typescript\nMi persona, trabajara con la version de 2019\n1 2 /* Language and Environment */ \u0026#34;target\u0026#34;: \u0026#34;es2019\u0026#34;, Agregaremos lo siguiente al final, entre el último y penúltimo corchete\n1 2 3 4 5 6 { \u0026#34;compilerOptions\u0026#34;: { ....contenido }, \u0026#34;exclude\u0026#34;: [\u0026#34;node_modules\u0026#34;] } Configuración de Nodemon En el directorio raiz del proyecto, crearemos el siguiente archivo.\nnodemon.json\nY agrearemos las siguientes lineas.\n1 2 3 4 5 6 7 8 { \u0026#34;watch\u0026#34;: [\u0026#34;src\u0026#34;], \u0026#34;ext\u0026#34;: \u0026#34;ts\u0026#34;, \u0026#34;ignore\u0026#34;: [ \u0026#34;src/**/*.spec.ts\u0026#34; ], \u0026#34;exec\u0026#34;: \u0026#34;ts-node \u0026#34; } watch : Indicamos que carpeta estará observando por cambios.\next : Cual serán las extención que estaremos observando por cambios.\nignore :\nIgnorar los que tengan las siguiente extención. exec :\nEl comando que ejecutará.\nConfigurando el Package.json En la sección de script agregaremos los siguiente comando para ejecución.\n1 2 3 4 5 \u0026#34;scripts\u0026#34;: { \u0026#34;build\u0026#34;: \u0026#34;tsc \u0026#34;, \u0026#34;dev\u0026#34;: \u0026#34;nodemon src/index.ts\u0026#34;, \u0026#34;start\u0026#34;: \u0026#34;npm run build \u0026amp;\u0026amp; node dist/index.js\u0026#34; }, dev : Este comando ejecutara el modo desarrollo, no compila a js , más puede ejecutar *.ts gracias a la configuracion que hicimos con nodemon.json build : Este comado compila los hay en la carpeta src start : Compila y ejecuta el proyecto compilado Estructura de carpetas La siguiente estrutura que se utilizará.\n(es básica, en el siguiente post, utilzaremos este mismo proyecto para realizar una API y luego una aplicacion web con la estrucura MVC)\nListo, empezemos a codificar! app.ts Dentro del archivo app.ts creamos un clase, para instanciar un servidor express.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 import express,{Application} from \u0026#34;express\u0026#34;; //Importamos express import * as dotenv from \u0026#34;dotenv\u0026#34; // Importamos dotenv (si tenemos variables de entorno) import morgan from \u0026#34;morgan\u0026#34;; //Importamos Morgan para logs por consola dotenv.config(); // Cargamos las variables de entorno /** * Clase Server */ export class Server{ private app:Application; /** * * @param port Especifica el puerto de escucha del servidor */ constructor(private port?: number| string) { this.app = express(); //Iniciamos express this.configuracion(); //Configuracion this.listen(); //Escuchamos this.middlewares(); //Middlewares this.route(); // Espones las Rutas disponibles } /** * Configuracion del puerto de escucha */ configuracion(){ this.app.set(\u0026#34;port\u0026#34;, this.port || process.env.PORT || 8080); } /** * Escuchamos por el puerto que configuramos */ async listen (){ await this.app.listen(this.app.get(\u0026#34;port\u0026#34;)); console.log(\u0026#34;Servidor escuchando en el puerto:\u0026#34;, this.app.get(\u0026#34;port\u0026#34;)); } //Middlewares middlewares(){ this.app.use(morgan(\u0026#34;dev\u0026#34;)); // Mesaje por consola de las peticiones con estilo dev , puede mirar la documentacion de morgan para ver mas detalles this.app.use(express.json()); // Permite leer el body de las peticiones // this.app.use(cors()); //Explicare en otro post por el momento // this.app.use(bodyParser.urlencoded({extended:true})); // this.app.use(cookieSession({ // name:\u0026#34;trxsalo\u0026#34;, // secret: process.env.SECRET, // httpOnly: true // })); } /** * Rutas expuestas */ route(){ this.app.use(\u0026#34;/\u0026#34;, express.static(\u0026#34;public\u0026#34;)); //Publico la carpeta public, servira un index.html } } 2.index.ts En el archivo index.ts dentro de la carpeta src agregáremos las siguiente lineas de código.\nEste será que levante el servidor express.\n1 2 3 4 5 import {Server} from \u0026#34;./app\u0026#34;; //importamos la clase server async function main(){ //funcion principal const app:Server = new Server(); //instanciamos la clase server } main(); //ejecutamos la funcion Public/ En la carpeta public , creamos un archivo index.html que serviremos por defecto en nuestro servidor.\n1 2 3 4 5 6 7 8 9 10 11 \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;meta name=\u0026#34;viewport\u0026#34; content=\u0026#34;width=device-width, initial-scale=1.0\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Page\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1\u0026gt;Hello World!!\u0026lt;/h1\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; Listo ya tenemos nuestro servidor Express escrito con typescript.\nYa podemos ejecutar el programa\n1 npm run dev Podemos verifcarlo por consola.\n1 2 3 4 5 6 7 8 \u0026gt; nodemon src/index.ts [nodemon] 3.0.2 [nodemon] to restart at any time, enter `rs` [nodemon] watching path(s): src/**/* [nodemon] watching extensions: ts [nodemon] starting `ts-node src/index.ts` Servidor escuchando en el puerto: 8080 Ahí nos ínidica en que puerto esta escuchando el servidor.\nVamos a nuestro navegador favorito, he ingresamos a la siguiente url\nlocalhost:(puerto que configuramos)\nSi vamos al link observaremos lo siguiente.\nListo ya temos nuestro servidor web sirviendo un documento html, ya tenéis su primera página web.\nLink del repositorio\nGithub\n","date":"2023-12-28T23:15:12-04:00","image":"https://blog.trxsalo.xyz/p/express-ts/img/express_huc1f39faf62fe7dce5cb7cedfb51cf89c_69682_120x120_fill_box_smart1_3.png","permalink":"https://blog.trxsalo.xyz/p/express-ts/","title":"Crea tu primer servidor web con Express y Typescript"},{"content":"Que es Hugo? Hugo es un generador de sitios estáticos, muy veloz y sencillo de usar. Los sitios generados con Hugo se pueden alojar en distinto servicio de hosting, como: Neflify, Heroku, GoDaddy, GitHub Pages, Amazon S3,entre otros muchos más. Hugo es ideal para crear un blog, un sitio de cartera o de empresa y documentación.\nNetlify Es un servicio de host en la nube, permite subir proyecto de una manera tan simple y rápida. En tan solo unos poco click, ya tendremos listo nuestro proyecto en la internet.\nConfigurando nuestro proyecto Hugo Una vez que tengamos listo nuestro proyecto para publicar, hay que hacer algunos cambios de los siguientes archivos, depende de tu proyecto Hugo o plantilla que escogiste.\nconfig.toml o theme.toml\nconfig.toml 1 2 # Change baseurl before deploy baseurl = \u0026#34;https://my-site-name.netlify.app\u0026#34; theme.toml 1 2 # Change baseurl before deploy homepage = \u0026#34;https://my-site-name.netlify.app\u0026#34; Lloguearse en Netlify En mi caso estaré logueandome con mi cuenta de GitHub\nSubir proyecto Una vez logueado, buscado la opción de new site, hacemos click sobre el botón, y seleccionamos, importar un proyecto existente, el proyecto Hugo que deseamos subir, debe estar ya en dicho repositorio que usan.\nMi persona seleccionara Deploy with GitHub\nLuego debemos buscar el proyecto que deseamos hacer el deploy.\nBuild settings Una ves selecionado el proyecto, se debe configurar para que se pueda realizar correctamente el build del proyecto.\nBase directory\nDiremos que será la raíz del proyecto \u0026quot;/\u0026quot;\nBuild command\nEl comando que debe ejecutarse\n1 hugo --gc --minify Publish directory Aquí escribiremos la ruta del directorio donde están los posts del blog. En Hugo por lo general están en la carpeta /content o dejar vacío por defecto. Listo ya esta para realizar el deploy Listo el proyecto Hugo ya esta. Cambiar nombre Netlify le asigna un nombre aleatorio al sitio que desplegamos, no dirigiremos a configuración del sitio y seleccionamos Cambiar nombre del sitio, ahí cambiamos al nombre que especificamos en la configuración de Hugo. Listo ya está nuestro sitio con Hugo desplegado.\n","date":"2023-11-11T00:00:00Z","image":"https://blog.trxsalo.xyz/p/hugo-public/img/hugo1_hu2e733ed0b312adc42a0548cedcd0212e_10205_120x120_fill_box_smart1_3.png","permalink":"https://blog.trxsalo.xyz/p/hugo-public/","title":"Como subir tu proyecto Hugo a Netlify"}]