Automatización profesional con Python 15 min lectura

Python Automation Capsule: convierte scripts sueltos en herramientas profesionales, portables y auditables

Aprende a convertir scripts Python sueltos en herramientas portables con uv, Typer, Pydantic, Ruff y pytest. Un método profesional para automatizar sin romper entornos ni perder control.

Por Equipo Starbyte

Python Automation Capsule: convierte scripts sueltos en herramientas profesionales, portables y auditables

El problema no es saber Python; el problema es que tus scripts no sobreviven fuera de tu computadora

Casi todos los que automatizan con Python han vivido esta escena.

Un script funciona perfecto en tu laptop.
Lo copias a otra máquina.
Falla por una librería.
Lo ejecutas en otro usuario.
Falla por la ruta del archivo.
Lo mandas a un compañero.
No tiene la misma versión de Python.
Lo programas en una tarea automática.
No encuentra dependencias.
Lo retomas tres meses después.
Ya no recuerdas qué instalaste.

El problema no era el código.

El problema era que el script no estaba encapsulado.

En automatización profesional, un script útil no debe depender de la memoria del autor. Debe traer consigo:

  • versión de Python;
  • dependencias;
  • validación de entrada;
  • comando claro;
  • configuración;
  • logs;
  • pruebas;
  • formato;
  • bloqueo de versiones;
  • instrucciones de ejecución.

A ese patrón lo llamaremos Python Automation Capsule.

No es un framework nuevo. Es un método de trabajo.

La idea es convertir un script suelto en una herramienta pequeña, portable y auditable.


Por qué este tema es actual

El ecosistema Python cambió mucho.

uv, de Astral, se está posicionando como un administrador moderno de Python, proyectos, entornos y dependencias escrito en Rust. Su documentación oficial lo describe como un gestor extremadamente rápido de paquetes y proyectos Python. Además, permite ejecutar scripts con dependencias inline, bloquear dependencias de scripts y solicitar versiones específicas de Python.

Ruff, también de Astral, reúne linting y formateo en una sola herramienta rápida. Su documentación oficial lo describe como un linter y formatter de Python escrito en Rust, y su formatter está pensado como reemplazo compatible con Black en la mayoría de casos.

Typer facilita crear interfaces de línea de comandos basadas en type hints. Su documentación oficial destaca que permite convertir funciones Python en comandos CLI intuitivos.

Pydantic permite validar datos y configuraciones con modelos tipados. pytest sigue siendo una base muy sólida para pruebas automatizadas en Python.

La tendencia no es “usar más librerías”.

La tendencia profesional es esta:

Automatizar menos como script improvisado y más como herramienta reproducible.


La idea propia: el método CÁPSULA

Una Python Automation Capsule debe tener siete capas.

C — Comando claro
A — Ambiente reproducible
P — Parámetros validados
S — Salidas controladas
U — Unit tests mínimos
L — Lint y formato
A — Auditoría de ejecución

Si falta una capa, el script todavía puede funcionar.
Pero será más frágil, más difícil de compartir y más peligroso de automatizar.

Vamos a construir una cápsula desde cero.

El caso será práctico: una herramienta que lee un CSV de entrada, valida columnas, normaliza datos y genera un archivo de salida con registro de errores.


Qué vas a construir

Crearás una herramienta llamada:

data-cleaner

Que podrá ejecutarse así:

uv run data-cleaner samples/clientes.csv --output output/clientes_limpios.csv

La herramienta hará esto:

1. leer archivo CSV;
2. validar que existan columnas obligatorias;
3. normalizar nombres;
4. validar correos;
5. registrar filas con errores;
6. generar archivo limpio;
7. dejar salidas auditables;
8. pasar pruebas;
9. ejecutarse igual en otra máquina.

No es un ejemplo decorativo. Es la base de cualquier automatización real: limpiar padrones, consolidar reportes, procesar exportaciones, validar bases de Excel, preparar datos para Power BI o ejecutar flujos desde n8n.


Estructura final del proyecto

La cápsula quedará así:

data-cleaner/
├── pyproject.toml
├── uv.lock
├── README.md
├── src/
│   └── data_cleaner/
│       ├── __init__.py
│       ├── cli.py
│       ├── models.py
│       └── processor.py
├── tests/
│   └── test_processor.py
├── samples/
│   └── clientes.csv
└── output/

Esta estructura evita el típico archivo eterno:

script_limpieza_final_final_ahora_si.py

Paso 1: crear el proyecto con uv

Instala uv según la documentación oficial de Astral.

En macOS/Linux:

curl -LsSf https://astral.sh/uv/install.sh | sh

En Windows PowerShell:

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Verifica:

uv --version

Crea el proyecto:

uv init data-cleaner --package
cd data-cleaner

Agrega dependencias:

uv add typer pydantic pandas

Agrega dependencias de desarrollo:

uv add --dev pytest ruff

Agrega soporte para validación de emails:

uv add "pydantic[email]"

Esto crea o actualiza:

pyproject.toml
uv.lock
.venv/

El archivo uv.lock es importante: permite reproducibilidad.


Paso 2: crear el comando CLI

Edita:

mkdir -p src/data_cleaner
touch src/data_cleaner/__init__.py
nano src/data_cleaner/cli.py

Código:

from pathlib import Path

import typer

from data_cleaner.processor import clean_file

app = typer.Typer(help="Limpia y valida archivos CSV de clientes.")


@app.command()
def run(
    input_file: Path = typer.Argument(..., help="Archivo CSV de entrada."),
    output: Path = typer.Option(
        Path("output/clientes_limpios.csv"),
        "--output",
        "-o",
        help="Archivo CSV de salida.",
    ),
) -> None:
    """
    Limpia un archivo CSV y genera una salida validada.
    """
    result = clean_file(input_file=input_file, output_file=output)

    typer.echo(f"Filas procesadas: {result.total_rows}")
    typer.echo(f"Filas válidas: {result.valid_rows}")
    typer.echo(f"Filas con error: {result.error_rows}")
    typer.echo(f"Salida: {output}")


if __name__ == "__main__":
    app()

Typer convierte esa función en un comando usable.


Paso 3: registrar el comando en pyproject.toml

Abre:

nano pyproject.toml

Agrega o ajusta:

[project.scripts]
data-cleaner = "data_cleaner.cli:app"

Ahora podrás ejecutar:

uv run data-cleaner --help

Este es el primer salto profesional: ya no ejecutas un archivo suelto, sino un comando.


Paso 4: crear modelos de validación con Pydantic

Crea:

nano src/data_cleaner/models.py

Código:

from pydantic import BaseModel, EmailStr, Field


class Cliente(BaseModel):
    nombre: str = Field(min_length=2)
    correo: EmailStr
    ciudad: str = Field(min_length=2)


class ProcessResult(BaseModel):
    total_rows: int
    valid_rows: int
    error_rows: int

Pydantic ayuda a evitar datos basura.

Sin validación, cualquier automatización termina llenando Excel o bases con errores silenciosos.


Paso 5: crear el procesador

Crea:

nano src/data_cleaner/processor.py

Código:

from pathlib import Path

import pandas as pd
from pydantic import ValidationError

from data_cleaner.models import Cliente, ProcessResult

REQUIRED_COLUMNS = {"nombre", "correo", "ciudad"}


def normalize_text(value: object) -> str:
    return str(value).strip()


def clean_file(input_file: Path, output_file: Path) -> ProcessResult:
    if not input_file.exists():
        raise FileNotFoundError(f"No existe el archivo: {input_file}")

    df = pd.read_csv(input_file)

    missing_columns = REQUIRED_COLUMNS - set(df.columns)
    if missing_columns:
        missing = ", ".join(sorted(missing_columns))
        raise ValueError(f"Faltan columnas obligatorias: {missing}")

    valid_rows: list[dict[str, str]] = []
    error_rows: list[dict[str, str]] = []

    for index, row in df.iterrows():
        raw_data = {
            "nombre": normalize_text(row["nombre"]).title(),
            "correo": normalize_text(row["correo"]).lower(),
            "ciudad": normalize_text(row["ciudad"]).title(),
        }

        try:
            cliente = Cliente(**raw_data)
            valid_rows.append(cliente.model_dump())
        except ValidationError as exc:
            error_rows.append(
                {
                    "fila": str(index + 2),
                    "error": exc.errors()[0]["msg"],
                    **raw_data,
                }
            )

    output_file.parent.mkdir(parents=True, exist_ok=True)
    pd.DataFrame(valid_rows).to_csv(output_file, index=False)

    if error_rows:
        errors_file = output_file.with_name(output_file.stem + "_errores.csv")
        pd.DataFrame(error_rows).to_csv(errors_file, index=False)

    return ProcessResult(
        total_rows=len(df),
        valid_rows=len(valid_rows),
        error_rows=len(error_rows),
    )

Aquí hay tres decisiones profesionales:

  1. no se procesa si faltan columnas;
  2. se separan filas válidas y erróneas;
  3. se genera archivo de errores en vez de ocultarlos.

Paso 6: crear datos de prueba

Crea:

mkdir -p samples output
nano samples/clientes.csv

Contenido:

nombre,correo,ciudad
ana torres,ana@example.com,piura
juan perez,correo_malo,lima
lu,lu@example.com,cusco
maria ruiz,maria@example.com,trujillo

Ejecuta:

uv run data-cleaner samples/clientes.csv --output output/clientes_limpios.csv

Deberías ver algo parecido a:

Filas procesadas: 4
Filas válidas: 3
Filas con error: 1
Salida: output/clientes_limpios.csv

Y tendrás:

output/clientes_limpios.csv
output/clientes_limpios_errores.csv

Paso 7: agregar pruebas con pytest

Crea:

mkdir -p tests
nano tests/test_processor.py

Código:

from pathlib import Path

import pandas as pd
import pytest

from data_cleaner.processor import clean_file


def test_clean_file_generates_valid_rows(tmp_path: Path) -> None:
    input_file = tmp_path / "clientes.csv"
    output_file = tmp_path / "salida.csv"

    input_file.write_text(
        "nombre,correo,ciudad\n"
        "ana torres,ana@example.com,piura\n"
        "juan perez,correo_malo,lima\n",
        encoding="utf-8",
    )

    result = clean_file(input_file=input_file, output_file=output_file)

    assert result.total_rows == 2
    assert result.valid_rows == 1
    assert result.error_rows == 1
    assert output_file.exists()

    df = pd.read_csv(output_file)
    assert df.iloc[0]["nombre"] == "Ana Torres"


def test_clean_file_fails_when_required_column_is_missing(tmp_path: Path) -> None:
    input_file = tmp_path / "clientes.csv"
    output_file = tmp_path / "salida.csv"

    input_file.write_text(
        "nombre,correo\n"
        "ana torres,ana@example.com\n",
        encoding="utf-8",
    )

    with pytest.raises(ValueError, match="Faltan columnas obligatorias"):
        clean_file(input_file=input_file, output_file=output_file)

Ejecuta:

uv run pytest

Esto cambia la naturaleza del script: ahora puedes modificarlo sin miedo.


Paso 8: agregar Ruff para formato y calidad

Ejecuta:

uv run ruff check .
uv run ruff format .

Ruff permite encontrar errores y formatear el código con una sola herramienta.

Agrega configuración en pyproject.toml:

[tool.ruff]
line-length = 100
target-version = "py312"

[tool.ruff.lint]
select = ["E", "F", "I", "B", "UP", "SIM"]

Esto activa reglas útiles:

E/F: errores básicos;
I: orden de imports;
B: posibles bugs;
UP: modernización de Python;
SIM: simplificación.

No actives 900 reglas el primer día. Empieza con reglas que aporten.


Paso 9: crear un README útil

Crea:

nano README.md

Contenido:

# data-cleaner

Herramienta CLI para limpiar y validar archivos CSV de clientes.

## Requisitos

- uv
- Python 3.12+

## Instalación

```bash
uv sync
```

## Uso

```bash
uv run data-cleaner samples/clientes.csv --output output/clientes_limpios.csv
```

## Pruebas

```bash
uv run pytest
```

## Formato y lint

```bash
uv run ruff check .
uv run ruff format .
```

## Columnas obligatorias

- nombre
- correo
- ciudad

## Salidas

- CSV limpio
- CSV de errores si existen filas inválidas

Un README así evita preguntas repetidas.


Paso 10: bloquear dependencias

Con uv, el archivo uv.lock debe quedar versionado.

Ejecuta:

uv lock

Luego:

git add pyproject.toml uv.lock src tests README.md samples
git commit -m "crear capsula de automatizacion Python"

Si otra persona clona el proyecto, puede ejecutar:

uv sync
uv run pytest
uv run data-cleaner samples/clientes.csv

Ese es el objetivo: reproducibilidad.


Paso 11: crear una variante ultraligera con script PEP 723

Hay casos donde no quieres un proyecto completo. Solo necesitas un script portable.

uv permite ejecutar scripts con dependencias inline.

Crea:

nano limpiar_clientes.py

Contenido:

# /// script
# requires-python = ">=3.12"
# dependencies = [
#   "pandas",
#   "pydantic[email]",
#   "typer",
# ]
# ///

from pathlib import Path

import pandas as pd
import typer
from pydantic import BaseModel, EmailStr, Field, ValidationError

app = typer.Typer()


class Cliente(BaseModel):
    nombre: str = Field(min_length=2)
    correo: EmailStr
    ciudad: str = Field(min_length=2)


@app.command()
def run(input_file: Path, output_file: Path = Path("clientes_limpios.csv")) -> None:
    df = pd.read_csv(input_file)
    valid_rows = []

    for _, row in df.iterrows():
        try:
            cliente = Cliente(
                nombre=str(row["nombre"]).strip().title(),
                correo=str(row["correo"]).strip().lower(),
                ciudad=str(row["ciudad"]).strip().title(),
            )
            valid_rows.append(cliente.model_dump())
        except ValidationError:
            continue

    pd.DataFrame(valid_rows).to_csv(output_file, index=False)
    typer.echo(f"Archivo generado: {output_file}")


if __name__ == "__main__":
    app()

Ejecuta:

uv run limpiar_clientes.py samples/clientes.csv --output-file output.csv

Bloquea dependencias del script:

uv lock --script limpiar_clientes.py

Esto crea:

limpiar_clientes.py.lock

Este patrón es muy útil para automatizaciones pequeñas: un solo archivo, dependencias declaradas y ejecución reproducible.


Cuándo usar proyecto y cuándo script inline

Caso Mejor opción
automatización pequeña script PEP 723 con uv
herramienta que usarán varios proyecto con pyproject
proceso con pruebas proyecto
tarea de una sola vez script inline
integración con CI/CD proyecto
entrega a otra área proyecto
prototipo rápido script inline

No todo necesita una arquitectura grande. Pero todo debería ser reproducible.


Mi método: la prueba de los 3 reinicios

Antes de considerar profesional una automatización, debe pasar tres reinicios.

Reinicio 1: otra terminal

Cierra terminal, abre otra y ejecuta:

uv run data-cleaner samples/clientes.csv

Reinicio 2: otra carpeta

Clona o copia el proyecto en otra ruta y ejecuta:

uv sync
uv run pytest

Reinicio 3: otra persona

Entrega el README a alguien y observa si puede ejecutarlo sin preguntarte.

Si falla, el problema no es la persona. Es la cápsula.


Automatización en GitHub Actions

Crea:

mkdir -p .github/workflows
nano .github/workflows/ci.yml

Contenido:

name: CI

on:
  pull_request:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Install uv
        uses: astral-sh/setup-uv@v6
        with:
          enable-cache: true

      - name: Set up Python
        run: uv python install 3.12

      - name: Install dependencies
        run: uv sync --all-extras --dev

      - name: Lint
        run: uv run ruff check .

      - name: Format check
        run: uv run ruff format --check .

      - name: Test
        run: uv run pytest

Este flujo bloquea errores antes de que lleguen a producción.


Trucos poco obvios que elevan el nivel

1. No aceptes rutas implícitas

Mal:

pd.read_csv("clientes.csv")

Bien:

def clean_file(input_file: Path, output_file: Path) -> ProcessResult:

Las rutas deben entrar como parámetros.

2. No ocultes errores en prints

Mal:

print("algo falló")

Bien:

raise ValueError("Faltan columnas obligatorias: correo")

3. No mezcles lectura, lógica y CLI

Separa:

cli.py → entrada del usuario
processor.py → lógica
models.py → validación
tests/ → pruebas

4. No escribas directo sobre el original

Nunca hagas:

clientes.csv → clientes.csv

Siempre genera salida nueva.

5. No pierdas las filas malas

Un archivo de errores vale oro. Sirve para corregir datos y auditar.

6. No dependas de Excel como validador

Valida antes de abrir en Excel.

7. No esperes a tener 100 pruebas

Dos pruebas buenas ya cambian el nivel del proyecto.


Checklist de una Python Automation Capsule

Capa Revisión Estado
Comando se ejecuta con un comando claro
Ambiente usa uv y lockfile
Parámetros rutas y opciones explícitas
Validación Pydantic o reglas claras
Salida genera archivo nuevo
Errores conserva filas problemáticas
Pruebas tiene pytest mínimo
Formato usa Ruff
README explica uso y comandos
Reproducibilidad otra persona puede ejecutarlo

Casos donde este método simplifica muchísimo

Scrapers

Cada scraper debería tener:

comando;
configuración;
reintentos;
salida;
log;
tests de parsing;
lockfile.

Procesamiento de Excel

Ideal para validar columnas, limpiar datos y generar salidas controladas.

Reportes automáticos

La cápsula evita que un reporte dependa de una ruta local o una versión de librería.

APIs internas

Typer puede servir para tareas administrativas, migraciones o mantenimiento.

n8n y tareas programadas

En vez de ejecutar scripts sueltos, n8n puede llamar comandos reproducibles.

Sector público

Muy útil para procesos donde necesitas trazabilidad y repetición: padrones, CUI, reportes, consolidación de datos, validación de Excel y generación de salidas.


Plantilla rápida para nuevos proyectos

Cada vez que empieces una automatización, usa esto:

uv init nombre-herramienta --package
cd nombre-herramienta
uv add typer pydantic pandas
uv add --dev pytest ruff
mkdir -p src/nombre_herramienta tests samples output

Luego crea:

cli.py
processor.py
models.py
tests/test_processor.py
README.md

Y no escribas lógica dentro del CLI.


Prompt experto para convertir un script suelto en cápsula

Actúa como arquitecto senior de automatización Python.

Voy a darte un script Python suelto. Quiero que lo conviertas en una Python Automation Capsule.

Reglas:
- Separar CLI, lógica y modelos.
- Usar uv para dependencias.
- Crear pyproject.toml.
- Crear comando con Typer.
- Validar entradas con Pydantic cuando aplique.
- Agregar pruebas con pytest.
- Agregar Ruff para lint y formato.
- Crear README con comandos.
- No sobrescribir archivos originales.
- Generar archivo de errores si hay datos inválidos.

Entrega:
1. estructura de carpetas;
2. pyproject.toml;
3. cli.py;
4. processor.py;
5. models.py;
6. tests;
7. README;
8. comandos de ejecución;
9. checklist de validación.

Plan de 7 días para profesionalizar tus scripts

Día 1: inventario

Elige tres scripts que uses de verdad.

Día 2: encapsula uno

Pásalo a proyecto con uv y Typer.

Día 3: valida entradas

Agrega Pydantic o reglas explícitas.

Día 4: separa errores

Genera archivo de errores o log.

Día 5: agrega pruebas

Cubre al menos caso exitoso y caso fallido.

Día 6: agrega Ruff

Formato y lint.

Día 7: entrega a otra persona

Si puede ejecutarlo con README, ya tienes una herramienta.


Idea clave

El salto profesional en Python no ocurre cuando escribes scripts más largos, sino cuando tus automatizaciones dejan de depender de tu memoria. Una Python Automation Capsule convierte un archivo suelto en una herramienta reproducible: comando claro, ambiente controlado, entradas validadas, salidas auditables, pruebas y formato. Eso simplifica trabajo real porque cualquier persona, servidor o flujo de automatización puede ejecutarla sin adivinar cómo fue construida.

Etiquetas: #python #uv #typer #pydantic #ruff #pytest #automatizacion #scripts-profesionales #cli #productividad #desarrollo #data-automation