Cierre 21-jul: B4 con sync histórico verificado y B6 aplicado; horas, bitácora y Excel de avance al día
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user