cuando termines y verifica que esté todo subido

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 01:33:35 +02:00
parent e1e9bb7499
commit a90b7443e4
20 changed files with 1855 additions and 2 deletions
+100
View File
@@ -0,0 +1,100 @@
---
name: upsert_xlsx_sheet
kind: function
lang: py
domain: infra
version: "1.0.0"
purity: impure
signature: "def upsert_xlsx_sheet(xlsx_path: str, sheet_name: str, records: list[dict], columns: list[str], key_col: str = \"\", preserve_cols: list[str] | None = None, formulas: dict | None = None, backup: bool = True, freeze: str = \"A2\", autofilter: bool = True) -> dict"
description: "Actualiza de forma NO DESTRUCTIVA una hoja concreta de un archivo .xlsx con openpyxl. Reescribe SOLO la hoja indicada (sheet_name) y conserva intactas las demas hojas del libro. Antes de limpiar la hoja gestionada lee, por una columna clave (key_col), los valores de las columnas de trabajo manual (preserve_cols) y los reescribe ganando sobre los datos nuevos. Cabecera estilizada (negrita, relleno, texto blanco, borde, centrado), freeze_panes, autofilter, auto-ancho de columnas, formulas por columna con placeholders {row} y {NombreColumna}, y backup .bak opcional. Devuelve un resumen con filas escritas, hojas conservadas y celdas manuales preservadas."
tags: [xlsx, openpyxl, spreadsheet, office, onlyoffice, infra]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [openpyxl]
params:
- name: xlsx_path
desc: "Ruta del archivo .xlsx. Si existe se abre con openpyxl (y se respaldan las demas hojas); si no existe se crea un libro nuevo eliminando la hoja por defecto vacia."
- name: sheet_name
desc: "Nombre de la hoja a (re)escribir. Es la UNICA hoja que la funcion toca; el resto del libro queda intacto. Si la hoja no existe, se crea."
- name: records
desc: "Lista de dicts; cada dict es una fila y sus claves son nombres de columna. Se escribe una fila por record en el orden definido por columns."
- name: columns
desc: "Orden canonico de columnas (nombres de cabecera). Define que columnas se escriben y en que orden. Una clave de un record que no este en columns se ignora."
- name: key_col
desc: "Nombre de la columna clave usada para emparejar filas existentes con records al preservar trabajo manual. Vacio (default) desactiva la preservacion. El match normaliza el valor (lowercase + espacios colapsados)."
- name: preserve_cols
desc: "Lista de columnas cuyos valores manuales existentes en el libro se conservan: si una celda ya tenia valor para esa clave, ese valor gana sobre el de records. None o lista vacia desactiva la preservacion."
- name: formulas
desc: "Dict opcional {columna: \"plantilla\"} o {columna: {\"f\": plantilla, \"fmt\": formato_numerico}}. La plantilla admite {row} (numero de fila actual) y {NombreColumna} (letra de la columna con ese nombre). Estas columnas se escriben como formula en cada fila y NO se rellenan desde records."
- name: backup
desc: "Si True y el archivo existe, copia a xlsx_path + '.bak' antes de escribir. El .bak es rotativo: cada llamada lo sobrescribe. Default True."
- name: freeze
desc: "Celda para freeze_panes (inmoviliza filas/columnas por encima/izquierda). Default 'A2' (congela la cabecera)."
- name: autofilter
desc: "Si True activa auto_filter sobre el rango A1 hasta la ultima columna y fila de datos. Default True."
output: "Dict con: sheet (nombre de la hoja escrita), rows_written (numero de filas de datos), other_sheets_preserved (lista con los nombres de las demas hojas conservadas), manual_cells_preserved (cuantas celdas de trabajo manual se conservaron) y backup_path (ruta del .bak creado, o cadena vacia si no hubo backup)."
tested: true
tests: ["test_no_destructivo_y_preserva_trabajo_manual", "test_crea_libro_nuevo_si_no_existe"]
test_file_path: "python/functions/infra/upsert_xlsx_sheet_test.py"
file_path: "python/functions/infra/upsert_xlsx_sheet.py"
---
## Ejemplo
```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from infra.upsert_xlsx_sheet import upsert_xlsx_sheet
result = upsert_xlsx_sheet(
xlsx_path="/home/enmanuel/afiliados/programas_afiliados.xlsx",
sheet_name="Datos",
records=[
{"Programa": "Awin", "Clicks": 1200, "Ingreso": 340},
{"Programa": "CJ", "Clicks": 800, "Ingreso": 210},
],
columns=["Programa", "Clicks", "Ingreso", "EPC"],
key_col="Programa",
preserve_cols=["Ingreso"], # el ingreso anotado a mano gana sobre el dato nuevo
formulas={
"EPC": {"f": '=IFERROR({Ingreso}{row}/{Clicks}{row},"")', "fmt": "0.00"},
},
)
print(result)
# {'sheet': 'Datos', 'rows_written': 2, 'other_sheets_preserved': ['Personal'],
# 'manual_cells_preserved': 1, 'backup_path': '/home/enmanuel/afiliados/programas_afiliados.xlsx.bak'}
```
La columna EPC se escribe como formula `=IFERROR(C2/B2,"")` (las letras se
resuelven a partir de la posicion de `Ingreso` y `Clicks` en `columns`), y la
hoja "Personal" del usuario no se toca.
## Cuando usarla
Usala cuando necesites regenerar UNA hoja de un .xlsx que el usuario tambien
edita a mano, sin destruir su trabajo: refrescar datos de research/scraping
manteniendo columnas anotadas manualmente, o publicar un dataset en una hoja
concreta de un libro que contiene otras hojas personales. Es la pieza de
escritura para flujos donde un editor (OnlyOffice/Excel) y un proceso
automatico comparten el mismo archivo.
## Gotchas
- **Impura — escribe en disco.** Pisa SOLO la hoja `sheet_name`; las demas hojas
del libro se conservan tal cual (esa es la garantia central de la funcion).
- **Requiere openpyxl** (ya instalado en `python/.venv`).
- **El .bak es rotativo**: cada llamada con `backup=True` sobrescribe
`xlsx_path + ".bak"`. No es un historial; es la copia de la version anterior.
- **Lee del disco.** Si el archivo esta abierto en OnlyOffice/Excel, GUARDA en
el editor ANTES de llamar a la funcion: ella lee la version en disco y, al
recargar el editor despues, perderias los cambios no guardados.
- **Las columnas se localizan por nombre en la cabecera (fila 1).** Si renombras
una columna entre ejecuciones, el match de `preserve_cols`/`key_col` con la
version anterior se rompe para esa columna (se trata como columna nueva).
- **`key_col` vacio o `preserve_cols` vacio** desactivan la preservacion: la hoja
se reescribe por completo desde `records`.
- Las columnas declaradas en `formulas` se escriben siempre como formula y NO se
rellenan desde `records` ni se preservan.