Files
balam/planeacion/generar_avance_2026_07_21.py

160 lines
7.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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": ("", "5061", 38.0, 0.65),
"Etapa 0": ("", "1822", 22.0, None),
"Sesión de arranque (kickoff)": ("", "2", 2.0, None),
"Sesión de Discovery": ("", "45", 5.5, None),
"Entrega y validación de accesos": ("", "1", 1.0, None),
"Validación técnica de la API": ("", "34", 4.5, None),
"Prototipo visual navegable": ("", "56", 6.0, None),
"Documento de hallazgos": ("", "23", 3.0, None),
"Sesión de validación del prototipo": ("", "1", 0.0, None),
"Etapa 1": ("", "3239", 16.0, 0.8),
"Demostración semanal": ("", "23", 1.5, None),
"Repositorio privado GitHub": ("", "1", 0.5, None),
"Backend base": ("B2", "67", 7.25, 1.0),
"Arquitectura multi-tenant": ("B1 + B6", "23", 2.0, 1.0),
"Cliente de la API de BIND": ("", "23", 2.0, None),
"Capa de escritura controlada": ("B7", "34", 0.0, None),
"Sincronización de clientes": ("B3", "2.53", 0.5, 1.0),
"Configuración de infraestructura en Azure": ("", "1", 0.0, None),
"Sincronización de facturas": ("B4", "4.55.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.54.5", 1.0, 0.85),
"CI/CD": ("", "1.52", 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)