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:
@@ -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.
|
||||
Reference in New Issue
Block a user