"""Genera Plan-actividades-avance-2026-07-27.xlsx a partir del del 21-jul. Aplica las horas y estatus reales al 27-jul: sesión de reglas (22-jul) y validación del prototipo (24-jul, APROBADO) completadas; sesión de API de Jira (27-jul) en curso; capa de escritura EN PAUSA a la espera de la definición del flujo de facturación (sesión 28-jul con Arturo/Araceli). Reutiliza el estilo/semáforo del archivo del 21-jul.""" 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-21.xlsx" DST = "/Users/johann/Desktop/Proyectos personales/BALAM/planeacion/Plan-actividades-avance-2026-07-27.xlsx" HOY = date(2026, 7, 27) CAFE, CREMA, GRIS_B, AMARILLO = "331F0E", "FDF3D8", "D9D9D9", "F7BD0C" 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 C_PCT, C_BLQ, C_EST, C_TRAB, C_STAT = 7, 8, 9, 10, 11 # Prefijo de la actividad -> overrides de datos (pct=%, trab=horas, comienzo/fin=fechas texto) OVERRIDES = { "Integracion de plataformas Balam": dict(pct=0.67, trab=40), "Etapa 0": dict(pct=1.0, trab=23), "Documento de hallazgos": dict(pct=1.0), "Sesión de validación del prototipo": dict(pct=1.0, trab=1.05, comienzo="vie 24/07/26", fin="vie 24/07/26"), "Etapa 1": dict(pct=0.82, trab=17), "Demostración semanal": dict(trab=1.75), "Sesión de reglas y dudas": dict(pct=1.0, trab=0.80), "Definición de alcance Jira": dict(pct=0.40, trab=0.20), } # Estatus a mano (gana sobre el semáforo automático): texto, fill, font, negrita K_ESPECIAL = { "Documento de hallazgos": ("✅ Completada", V_FILL, V_FONT, False), "Sesión de validación del prototipo": ("✅ Validado (aprobado)", V_FILL, V_FONT, True), "Demostración semanal": ("🟡 En curso (semanal)", A_FILL, A_FONT, False), "Modelo de facturas": ("🟡 En curso · falta seeder", A_FILL, A_FONT, False), "Capa de escritura controlada": ("🔴 En pausa · def. Balam 28-jul", R_FILL, R_FONT, True), "Sesión de reglas y dudas": ("✅ Completada", V_FILL, V_FONT, False), "Definición de alcance Jira": ("🟡 En curso · API Jira 27-jul", A_FILL, A_FONT, False), "Configuración de infraestructura en Azure": ("🟡 Pendiente (Balam)", A_FILL, A_FONT, False), "CI/CD": ("🔴 Pendiente · dep. Azure", R_FILL, R_FONT, True), } 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 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") wb = openpyxl.load_workbook(SRC) ws = wb.active # 1) Aplica overrides de datos por prefijo de nombre for row in range(2, ws.max_row + 1): nombre = str(ws.cell(row=row, column=1).value or "").strip() if not nombre: continue for prefijo, ov in OVERRIDES.items(): if nombre.startswith(prefijo): if "pct" in ov: ws.cell(row=row, column=C_PCT, value=ov["pct"]) if "trab" in ov: ws.cell(row=row, column=C_TRAB, value=ov["trab"]) if "comienzo" in ov: ws.cell(row=row, column=3, value=ov["comienzo"]) if "fin" in ov: ws.cell(row=row, column=4, value=ov["fin"]) break # 2) Recalcula el semáforo con HOY=27-jul (mismas reglas que el del 21-jul) for row in range(2, ws.max_row + 1): nombre = str(ws.cell(row=row, column=1).value or "").strip() if not nombre or nombre.startswith("✅") or nombre.startswith("▬"): continue es_etapa = nombre.startswith("Etapa") or nombre.startswith("Integracion") pct = ws.cell(row=row, column=C_PCT).value or 0 fin = fecha(ws.cell(row=row, column=4).value) ini = fecha(ws.cell(row=row, column=3).value) stat = ws.cell(row=row, column=C_STAT) if es_etapa: 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 en la celda de % pcel = ws.cell(row=row, column=C_PCT) 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) pcel.number_format = "0%" ws.cell(row=row, column=C_TRAB).number_format = "0.0" # 3) Overrides finos de estatus (bloqueos y dependencias) for row in range(2, ws.max_row + 1): nombre = str(ws.cell(row=row, column=1).value or "").strip() for prefijo, (txt, fill, font, bold) in K_ESPECIAL.items(): if nombre.startswith(prefijo): stat = ws.cell(row=row, column=C_STAT) stat.value = txt pinta(stat, fill, font, bold) break # 4) Encabezado de la columna de estatus y pie ws.cell(row=1, column=C_STAT, value=f"Estatus (hoy {HOY.day}-jul)") # 5) Reescribe leyenda/notas (borra las viejas de la línea de HOY del 21-jul) for row in range(ws.max_row, 1, -1): v = str(ws.cell(row=row, column=1).value or "") if v.startswith("✅ Completada ·") or v.startswith("▬ La línea roja"): for col in range(1, C_STAT + 1): ws.cell(row=row, column=col).value = None ley = ws.max_row + 2 notas = [ ("✅ Completada · 🟡 En curso · 🔴 Vencida/Pendiente · ⚪ Próxima", G_FONT, True), ("Núcleo de la Etapa 1 construido y verificado contra producción: autenticación y roles, " "bitácora, multi-tenant con RLS, cliente de BIND, sincronización de clientes/facturas/" "cotizaciones, modelo de facturas con estados y cartera por moneda.", CAFE, False), ("🔴 La capa de escritura (cotización→factura) queda EN PAUSA a la espera de la definición " "del flujo de facturación (sesión del martes 28-jul con Arturo y Araceli).", "9C0006", False), ("API de Jira: token recibido el 27-jul; la integración inicia una vez definido el flujo.", CAFE, False), ] for i, (txt, color, bold) in enumerate(notas): c = ws.cell(row=ley + i, column=1, value=txt) c.font = Font(italic=True, size=9, color=color, bold=bold) ws.freeze_panes = "A2" ws.oddFooter.center.text = "Avance al 27-jul-2026 · Horas conforme al control local (pendiente conciliar con Jira)" wb.save(DST) print("OK ->", DST)