Etapa 1: plan de ejecución, corte de avance del 16-jul y fuentes
- Plan-Etapa1.md: plan detallado por fases/bloques (B0-B9) con horas, decisiones de arquitectura y lo bloqueado hasta el 22-jul - Avance-Etapa1-2026-07-16.md + Excel de avance ordenado por fechas (con script generador y de ordenamiento) - WhatsApp 16-jul: invitación al Git recibida, procedimientos de clientes enviados por correo, Erika pide avance de Etapa 1 - Actualiza PENDIENTES y REGISTRO
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
"""Ordena las actividades del Excel de avance por fecha de Comienzo, dentro de
|
||||
cada etapa, conservando estilos, formatos, fórmulas de resumen y encabezados.
|
||||
|
||||
Estructura del plan:
|
||||
fila 2 = Integración (resumen maestro) -> fija
|
||||
fila 3 = Etapa 0 (encabezado) -> fija
|
||||
filas 4-10 = tareas de Etapa 0 -> se ordenan por Comienzo
|
||||
fila 11 = Etapa 1 (encabezado) -> fija
|
||||
filas 12-24 = tareas de Etapa 1 -> se ordenan por Comienzo
|
||||
|
||||
Las celdas de fecha son texto ("mié 01/07/26"), así que se parsea el dd/mm/yy
|
||||
para ordenar cronológicamente (no alfabéticamente, que ordenaría por día de
|
||||
semana). En empate de Comienzo, se desempata por Fin y luego por el orden
|
||||
original (estable), para no barajar tareas que arrancan el mismo día.
|
||||
"""
|
||||
|
||||
import re
|
||||
from copy import copy
|
||||
from pathlib import Path
|
||||
|
||||
import openpyxl
|
||||
|
||||
DIRECTORY = Path(__file__).parent
|
||||
TARGET = DIRECTORY / "Plan-actividades-avance-2026-07-16.xlsx"
|
||||
|
||||
# Grupos de filas de tareas a ordenar (rango inclusivo). Los encabezados
|
||||
# (2, 3, 11) quedan fuera y no se mueven.
|
||||
GROUPS = [(4, 10), (12, 24)]
|
||||
|
||||
DATE_RE = re.compile(r"(\d{2})/(\d{2})/(\d{2})")
|
||||
|
||||
|
||||
def sort_key_from_cell(value):
|
||||
"""Devuelve (aaaa, mm, dd) desde 'mié 01/07/26'; None si no hay fecha."""
|
||||
if value is None:
|
||||
return (9999, 99, 99)
|
||||
m = DATE_RE.search(str(value))
|
||||
if not m:
|
||||
return (9999, 99, 99)
|
||||
dd, mm, yy = (int(g) for g in m.groups())
|
||||
return (2000 + yy, mm, dd)
|
||||
|
||||
|
||||
def snapshot_row(sheet, row, max_col):
|
||||
"""Captura valor + estilo + formato de cada celda de la fila, y el alto."""
|
||||
cells = []
|
||||
for col in range(1, max_col + 1):
|
||||
c = sheet.cell(row, col)
|
||||
cells.append(
|
||||
{
|
||||
"value": c.value,
|
||||
"style": copy(c._style),
|
||||
"number_format": c.number_format,
|
||||
}
|
||||
)
|
||||
height = sheet.row_dimensions[row].height
|
||||
return {"cells": cells, "height": height}
|
||||
|
||||
|
||||
def restore_row(sheet, row, snap, max_col):
|
||||
for col in range(1, max_col + 1):
|
||||
c = sheet.cell(row, col)
|
||||
data = snap["cells"][col - 1]
|
||||
c.value = data["value"]
|
||||
c._style = copy(data["style"])
|
||||
c.number_format = data["number_format"]
|
||||
if snap["height"] is not None:
|
||||
sheet.row_dimensions[row].height = snap["height"]
|
||||
|
||||
|
||||
def main():
|
||||
wb = openpyxl.load_workbook(TARGET)
|
||||
sh = wb["Plan"]
|
||||
max_col = sh.max_column
|
||||
|
||||
for start, end in GROUPS:
|
||||
snaps = [snapshot_row(sh, r, max_col) for r in range(start, end + 1)]
|
||||
# Orden estable: por (Comienzo, Fin) usando el índice original como
|
||||
# tercer criterio implícito de estabilidad de sorted().
|
||||
order = sorted(
|
||||
range(len(snaps)),
|
||||
key=lambda i: (
|
||||
sort_key_from_cell(snaps[i]["cells"][2]["value"]), # col C Comienzo
|
||||
sort_key_from_cell(snaps[i]["cells"][3]["value"]), # col D Fin
|
||||
),
|
||||
)
|
||||
reordered = [snaps[i] for i in order]
|
||||
for offset, snap in enumerate(reordered):
|
||||
restore_row(sh, start + offset, snap, max_col)
|
||||
|
||||
tmp = TARGET.with_suffix(".tmp.xlsx")
|
||||
wb.save(tmp)
|
||||
|
||||
# Verificación: cada grupo queda no-decreciente por Comienzo.
|
||||
check = openpyxl.load_workbook(tmp)["Plan"]
|
||||
for start, end in GROUPS:
|
||||
prev = (0, 0, 0)
|
||||
for r in range(start, end + 1):
|
||||
k = sort_key_from_cell(check.cell(r, 3).value)
|
||||
assert k >= prev, f"Fila {r} fuera de orden: {k} < {prev}"
|
||||
prev = k
|
||||
# Los encabezados de etapa siguen en su lugar.
|
||||
assert check.cell(3, 1).value == "Etapa 0"
|
||||
assert check.cell(11, 1).value == "Etapa 1"
|
||||
# Las fórmulas de resumen se conservan.
|
||||
assert str(check.cell(11, 8).value).startswith("=SUM")
|
||||
|
||||
tmp.replace(TARGET)
|
||||
print("Ordenado correctamente:", TARGET.name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user