"""Genera Plan-actividades-avance-2026-07-21.xlsx desde el del 16-jul. Columnas nuevas: Bloque · Horas estimadas (rango) · Horas trabajadas · Estatus. Semáforo: verde=completada, amarillo=en curso, rojo=vencida, gris=próxima. Línea roja gruesa = HOY (21-jul): lo de arriba debería estar hecho o en curso.""" import re from datetime import date import openpyxl from openpyxl.styles import Font, Alignment, PatternFill, Border, Side SRC = "/Users/johann/Desktop/Proyectos personales/BALAM/planeacion/Plan-actividades-avance-2026-07-16.xlsx" DST = "/Users/johann/Desktop/Proyectos personales/BALAM/planeacion/Plan-actividades-avance-2026-07-21.xlsx" HOY = date(2026, 7, 21) AMARILLO, CAFE = "F7BD0C", "331F0E" CREMA, GRIS_B = "FDF3D8", "D9D9D9" V_FILL, V_FONT = "C6EFCE", "006100" # verde A_FILL, A_FONT = "FFEB9C", "9C6500" # amarillo R_FILL, R_FONT = "FFC7CE", "9C0006" # rojo G_FILL, G_FONT = "F2F2F2", "595959" # gris # prefijo -> (bloque, estimadas_rango, trabajadas, nuevo_pct o None) MAPA = { "Integracion de plataformas Balam": ("", "50–61", 38.0, 0.65), "Etapa 0": ("", "18–22", 22.0, None), "Sesión de arranque (kickoff)": ("—", "2", 2.0, None), "Sesión de Discovery": ("—", "4–5", 5.5, None), "Entrega y validación de accesos": ("—", "1", 1.0, None), "Validación técnica de la API": ("—", "3–4", 4.5, None), "Prototipo visual navegable": ("—", "5–6", 6.0, None), "Documento de hallazgos": ("—", "2–3", 3.0, None), "Sesión de validación del prototipo": ("—", "1", 0.0, None), "Etapa 1": ("", "32–39", 16.0, 0.8), "Demostración semanal": ("—", "2–3", 1.5, None), "Repositorio privado GitHub": ("—", "1", 0.5, None), "Backend base": ("B2", "6–7", 7.25, 1.0), "Arquitectura multi-tenant": ("B1 + B6", "2–3", 2.0, 1.0), "Cliente de la API de BIND": ("—", "2–3", 2.0, None), "Capa de escritura controlada": ("B7", "3–4", 0.0, None), "Sincronización de clientes": ("B3", "2.5–3", 0.5, 1.0), "Configuración de infraestructura en Azure": ("—", "1", 0.0, None), "Sincronización de facturas": ("B4", "4.5–5.5", 1.0, 1.0), "Sesión de reglas y dudas": ("—", "1", 0.0, None), "Definición de alcance Jira": ("—", "1", 0.0, None), "Modelo de facturas": ("B1 + B4 + B9", "3.5–4.5", 1.0, 0.85), "CI/CD": ("—", "1.5–2", 0.0, None), } def fecha(celda): m = re.search(r"(\d{2})/(\d{2})/(\d{2})", str(celda or "")) return date(2000 + int(m.group(3)), int(m.group(2)), int(m.group(1))) if m else None wb = openpyxl.load_workbook(SRC) ws = wb.active C_BLQ, C_EST, C_TRAB, C_STAT = 8, 9, 10, 11 for col, titulo in [(C_BLQ, "Bloque (plan interno)"), (C_EST, "Horas estimadas"), (C_TRAB, "Horas trabajadas"), (C_STAT, f"Estatus (hoy {HOY.day}-jul)")]: ws.cell(row=1, column=col, value=titulo) thin = Side(style="thin", color=GRIS_B) borde = Border(left=thin, right=thin, top=thin, bottom=thin) def pinta(celda, fill, font_color, bold=False): celda.fill = PatternFill("solid", fgColor=fill) celda.font = Font(color=font_color, bold=bold, size=10) celda.alignment = Alignment(horizontal="center", vertical="center") fila_hoy = None # primera actividad de Etapa 1 que arranca DESPUÉS de hoy en_etapa1 = False for row in range(2, ws.max_row + 1): nombre = str(ws.cell(row=row, column=1).value or "").strip() if not nombre: continue if nombre.startswith("Etapa 1"): en_etapa1 = True for prefijo, (blq, est, trab, pct) in MAPA.items(): if nombre.startswith(prefijo): ws.cell(row=row, column=C_BLQ, value=blq) ws.cell(row=row, column=C_EST, value=est) ws.cell(row=row, column=C_TRAB, value=trab) if pct is not None: ws.cell(row=row, column=7, value=pct) break es_etapa = nombre.startswith("Etapa") or nombre.startswith("Integracion") pct = ws.cell(row=row, column=7).value or 0 ini, fin = fecha(ws.cell(row=row, column=3).value), fecha(ws.cell(row=row, column=4).value) stat = ws.cell(row=row, column=C_STAT) if es_etapa: ws.cell(row=row, column=C_STAT, value="") elif pct >= 1: stat.value = "✅ Completada"; pinta(stat, V_FILL, V_FONT) elif fin and fin < HOY: stat.value = "🔴 Vencida"; pinta(stat, R_FILL, R_FONT, bold=True) elif ini and ini <= HOY: stat.value = "🟡 En curso"; pinta(stat, A_FILL, A_FONT) elif pct > 0: stat.value = "🟢 Adelantada"; pinta(stat, V_FILL, V_FONT) else: stat.value = "⚪ Próxima"; pinta(stat, G_FILL, G_FONT) # semáforo también en la celda de % pcel = ws.cell(row=row, column=7) if not es_etapa: if pct >= 1: pinta(pcel, V_FILL, V_FONT, bold=True) elif pct > 0: pinta(pcel, A_FILL, A_FONT, bold=True) elif fin and fin < HOY: pinta(pcel, R_FILL, R_FONT, bold=True) if en_etapa1 and fila_hoy is None and not es_etapa and ini and ini > HOY: fila_hoy = row # estilo general: encabezado marca, filas de etapa, bordes, formatos head_fill = PatternFill("solid", fgColor=AMARILLO) etapa_fill = PatternFill("solid", fgColor=CREMA) for row in range(1, ws.max_row + 1): nombre = str(ws.cell(row=row, column=1).value or "") es_etapa = nombre.startswith("Etapa") or nombre.startswith("Integracion") for col in range(1, C_STAT + 1): c = ws.cell(row=row, column=col) c.border = borde if row == 1: c.fill = head_fill c.font = Font(bold=True, color=CAFE, size=11) c.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True) elif es_etapa and nombre: c.fill = etapa_fill c.font = Font(bold=True, color=CAFE) if row > 1: ws.cell(row=row, column=7).number_format = "0%" ws.cell(row=row, column=C_TRAB).number_format = "0.0" for col in (C_BLQ, C_EST, C_TRAB): ws.cell(row=row, column=col).alignment = Alignment(horizontal="center") # línea de HOY: borde rojo grueso arriba de la primera actividad futura de Etapa 1 if fila_hoy: rojo = Side(style="thick", color="C00000") for col in range(1, C_STAT + 1): c = ws.cell(row=fila_hoy, column=col) c.border = Border(left=c.border.left, right=c.border.right, top=rojo, bottom=c.border.bottom) # leyenda ley = ws.max_row + 2 ws.cell(row=ley, column=1, value="✅ Completada · 🟡 En curso · 🔴 Vencida (fecha plan pasada, <100%) · ⚪ Próxima").font = Font(italic=True, size=9, color=G_FONT) ws.cell(row=ley + 1, column=1, value=f"▬ La línea roja gruesa marca HOY ({HOY.day}-jul): lo de arriba debería estar terminado o en curso.").font = Font(italic=True, size=9, color="C00000") for letra, ancho in {"A": 48, "B": 9, "C": 12, "D": 12, "E": 12, "F": 22, "G": 12, "H": 15, "I": 14, "J": 14, "K": 20}.items(): ws.column_dimensions[letra].width = ancho ws.freeze_panes = "A2" ws.oddFooter.center.text = "Avance al 21-jul-2026 · Horas conforme al control local (pendiente conciliar con Jira)" wb.save(DST) print("OK ->", DST, "| línea de hoy en fila:", fila_hoy)