"""Genera Avance-Balam-2026-07-27.xlsx — versión SIMPLE para enviar a Erika. Se construye desde el Excel interno (`Plan-actividades-avance-2026-07-27.xlsx`), que a su vez lee las horas de `Seguimiento-horas.csv`. Así los dos archivos nunca se contradicen: este es una vista reducida, no una segunda fuente de datos. Formato pedido por Erika el 21-jul: entregable + actividad + horas por etapa. Se quitan del interno: rangos estimados, bloques del plan (B1/B4/B9), predecesoras y duración en días — ruido para quien solo da seguimiento. """ import os import re from datetime import date import openpyxl from openpyxl.styles import Alignment, Border, Font, PatternFill, Side from openpyxl.utils import get_column_letter BASE = os.path.dirname(os.path.abspath(__file__)) SRC = os.path.join(BASE, "Plan-actividades-avance-2026-07-27.xlsx") DST = os.path.join(BASE, "Avance-Balam-2026-07-27.xlsx") HOY = date(2026, 7, 27) # Marca Balam CAFE, AMARILLO, CREMA = "331F0E", "F7BD0C", "FDF3D8" V_FILL, V_FONT = "C6EFCE", "006100" # verde A_FILL, A_FONT = "FFEB9C", "9C6500" # amarillo R_FILL, R_FONT = "FFC7CE", "9C0006" # rojo GRIS = "595959" BORDE = "D9D9D9" # Columnas del origen O_NOMBRE, O_INI, O_FIN, O_PCT, O_TRAB, O_STAT = 1, 3, 4, 7, 10, 11 thin = Side(style="thin", color=BORDE) box = Border(left=thin, right=thin, top=thin, bottom=thin) def limpia_fecha(txt): """'vie 24/07/26' -> '24/07/2026'.""" m = re.search(r"(\d{2})/(\d{2})/(\d{2})", str(txt or "")) return f"{m.group(1)}/{m.group(2)}/20{m.group(3)}" if m else "" def es_etapa(nombre): return nombre.startswith(("Etapa", "Integracion")) # --- Lee el interno ----------------------------------------------------------- wsrc = openpyxl.load_workbook(SRC).active filas = [] for row in range(2, wsrc.max_row + 1): nombre = str(wsrc.cell(row=row, column=O_NOMBRE).value or "").strip() if not nombre or nombre.startswith(("✅", "▬", "Horas:", "Etapa 1:", "🔴", "Pendientes", "API de Jira", "Núcleo")): continue filas.append({ "nombre": nombre, "ini": limpia_fecha(wsrc.cell(row=row, column=O_INI).value), "fin": limpia_fecha(wsrc.cell(row=row, column=O_FIN).value), "pct": wsrc.cell(row=row, column=O_PCT).value or 0, "trab": wsrc.cell(row=row, column=O_TRAB).value, "stat": str(wsrc.cell(row=row, column=O_STAT).value or "").strip(), "etapa": es_etapa(nombre), }) # --- Construye el simple ------------------------------------------------------ wb = openpyxl.Workbook() ws = wb.active ws.title = "Avance" ws["A1"] = "Proyecto Balam · Plataforma de Automatización Financiera" ws["A1"].font = Font(bold=True, size=14, color=CAFE) ws["A2"] = f"Avance al {HOY.day}-jul-2026" ws["A2"].font = Font(size=10, italic=True, color=GRIS) ENC = ["Actividad", "Inicio", "Fin", "Avance", "Horas", "Estatus"] ANCHOS = [52, 12, 12, 10, 9, 34] FILA_ENC = 4 for i, (txt, ancho) in enumerate(zip(ENC, ANCHOS), start=1): c = ws.cell(row=FILA_ENC, column=i, value=txt) c.font = Font(bold=True, size=10, color=CAFE) c.fill = PatternFill("solid", fgColor=AMARILLO) c.alignment = Alignment(horizontal="center", vertical="center") c.border = box ws.column_dimensions[get_column_letter(i)].width = ancho r = FILA_ENC + 1 for f in filas: fill_etapa = PatternFill("solid", fgColor=CREMA) if f["etapa"] else None c = ws.cell(row=r, column=1, value=f["nombre"]) c.font = Font(bold=f["etapa"], size=10, color=CAFE) c.alignment = Alignment(vertical="center", wrap_text=True, indent=0 if f["etapa"] else 1) for col, val in ((2, f["ini"]), (3, f["fin"])): cc = ws.cell(row=r, column=col, value=val) cc.font = Font(size=9, color=GRIS) cc.alignment = Alignment(horizontal="center", vertical="center") # Avance cp = ws.cell(row=r, column=4, value=f["pct"]) cp.number_format = "0%" cp.alignment = Alignment(horizontal="center", vertical="center") if f["pct"] >= 1: cp.font = Font(bold=True, size=10, color=V_FONT) cp.fill = PatternFill("solid", fgColor=V_FILL) elif f["pct"] > 0: cp.font = Font(bold=True, size=10, color=A_FONT) cp.fill = PatternFill("solid", fgColor=A_FILL) else: cp.font = Font(size=10, color=GRIS) # Horas ch = ws.cell(row=r, column=5, value=f["trab"]) ch.number_format = "0.00" ch.font = Font(bold=f["etapa"], size=10, color=CAFE) ch.alignment = Alignment(horizontal="center", vertical="center") # Estatus cs = ws.cell(row=r, column=6, value=f["stat"]) cs.alignment = Alignment(horizontal="left", vertical="center", wrap_text=True) cs.font = Font(size=9, color=GRIS) if f["stat"].startswith("✅"): cs.font = Font(size=9, color=V_FONT); cs.fill = PatternFill("solid", fgColor=V_FILL) elif f["stat"].startswith("🟡"): cs.font = Font(size=9, color=A_FONT); cs.fill = PatternFill("solid", fgColor=A_FILL) elif f["stat"].startswith("🔴"): cs.font = Font(size=9, bold=True, color=R_FONT); cs.fill = PatternFill("solid", fgColor=R_FILL) for col in range(1, 7): ws.cell(row=r, column=col).border = box if fill_etapa and col in (1, 2, 3): ws.cell(row=r, column=col).fill = fill_etapa r += 1 # --- Notas -------------------------------------------------------------------- tot = next((f["trab"] for f in filas if f["nombre"].startswith("Integracion")), 0) e0 = next((f["trab"] for f in filas if f["nombre"].startswith("Etapa 0")), 0) e1 = next((f["trab"] for f in filas if f["nombre"].startswith("Etapa 1")), 0) notas = [ ("", False), (f"Horas trabajadas: {e0:.2f} h en Etapa 0 y {e1:.2f} h en Etapa 1 " f"({tot:.2f} h en total).", True), ("Etapa 1: 6 de los 8 entregables terminados — autenticación y roles con bitácora, " "multi-tenant, cliente de la API de BIND, sincronización de clientes, " "sincronización de facturas y cotizaciones, y modelo de facturas con estados y " "cartera por moneda. Verificado contra producción: 1,494 facturas sincronizadas " "con la cartera cuadrada.", False), ("La capa de escritura (cotización→factura) está en pausa a la espera de la " "definición del flujo de facturación (sesión del martes 28-jul).", False), ("Azure y CI/CD esperan la configuración de los recursos del lado de Balam.", False), ] for txt, bold in notas: if txt: c = ws.cell(row=r, column=1, value=txt) c.font = Font(italic=True, size=9, color=CAFE, bold=bold) c.alignment = Alignment(wrap_text=True, vertical="top") ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=6) ws.row_dimensions[r].height = 28 if len(txt) > 110 else 14 r += 1 ws.freeze_panes = f"A{FILA_ENC + 1}" ws.sheet_view.showGridLines = False ws.print_area = f"A1:F{r}" ws.page_setup.orientation = "landscape" ws.page_setup.fitToWidth = 1 ws.sheet_properties.pageSetUpPr.fitToPage = True wb.save(DST) print("OK ->", DST) print(f" {len(filas)} filas · Etapa 0 {e0:.2f} h · Etapa 1 {e1:.2f} h · total {tot:.2f} h")