From 69e06dfd8c0e8bc62b1b5050c36f0c36c9e6bf0a Mon Sep 17 00:00:00 2001 From: Johann Date: Wed, 16 Sep 2026 10:24:56 -0600 Subject: [PATCH] Corte del 16-sep (94.58 h): +0.50 h por el Excel publicado en Google Sheets; el Sheet compartido pasa a llamarse Avance-Balam y conserva su enlace Co-Authored-By: Claude Fable 5.1 --- planeacion/Avance-Balam-2026-09-16.xlsx | Bin 0 -> 11025 bytes planeacion/Seguimiento-horas.csv | 1 + planeacion/Seguimiento-horas.md | 10 +- planeacion/generar_avance_2026_09_16.py | 171 ++++++++++++++++++++++++ 4 files changed, 177 insertions(+), 5 deletions(-) create mode 100644 planeacion/Avance-Balam-2026-09-16.xlsx create mode 100644 planeacion/generar_avance_2026_09_16.py diff --git a/planeacion/Avance-Balam-2026-09-16.xlsx b/planeacion/Avance-Balam-2026-09-16.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..71c1620851833b3d6ff93a50ef5075be3f6c2c9e GIT binary patch literal 11025 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 "2026-09-15": + nuevas += h + if r["fecha"] > "2026-08-06": + desde_6ago += h + +E0, E1, E3 = por_etapa["0"], por_etapa["1"], por_etapa["3"] +AJ = por_act["AJ-01"] +E2 = por_etapa["2"] - AJ +TOTAL = E0 + E1 + E2 + AJ + E3 +assert abs(TOTAL - CORTE_ANTERIOR - nuevas) < 0.01, "El CSV no arranca del corte del 15-sep" +PCT_GLOBAL = round(TOTAL / ((119 + 146) / 2), 2) + +# E1-05 +0.50 (Excel en Google Sheets + corte del 16-sep) → gestión y cortes. +DELTAS_TRAB = {"Demostración semanal": 0.50} +assert abs(sum(DELTAS_TRAB.values()) - nuevas) < 0.01, ( + f"El reparto ({sum(DELTAS_TRAB.values()):.2f}) no suma las {nuevas:.2f} h nuevas del CSV") + +ENCABEZADOS = { + "Integracion de plataformas Balam": (TOTAL, PCT_GLOBAL), + "Etapa 0": (E0, 1.0), + "Etapa 1": (E1, 0.95), + "Etapa 2": (E2, 0.70), + "» Ajuste dentro de la Etapa 2": (AJ, 0.85), + "Etapa 3": (E3, 0.05), +} + +FILAS = { + "Demostración semanal": (0.6, "🟡 En curso · el seguimiento pasó a Pedro (sesiones 'Seguimiento PAF' " + "21, 24 y 26-ago; 3, 9 y 10-sep) · corte de horas al 16-sep, publicado en " + "Google Sheets", A_FILL, A_FONT, False), +} + + +def pinta(celda, fill, font_color, bold=False, size=9, align="left"): + celda.fill = PatternFill("solid", fgColor=fill) + celda.font = Font(color=font_color, bold=bold, size=size) + celda.alignment = Alignment(horizontal=align, vertical="center", wrap_text=True) + + +wb = openpyxl.load_workbook(SRC) +ws = wb.active + +ws["A2"] = ("Plan y avance al 16-sep-2026 · plataforma publicada en paputec.mx (provisional), cotización real " + "desde el ticket (individual y en lote); prefactura por API bloqueada por BIND · horas del " + "10-ago al 16-sep reconstruidas, pendientes de validar") +ws["A2"].font = Font(size=10, italic=True, color=GRIS) + +# Notas del corte anterior: se limpian antes del bucle y se reescriben abajo. +primera_nota = None +for row in range(5, ws.max_row + 1): + v = str(ws.cell(row=row, column=1).value or "") + if v.startswith("✅ Completada ·"): + primera_nota = row + if primera_nota and row >= primera_nota: + for rango in list(ws.merged_cells.ranges): + if rango.min_row == row: + ws.unmerge_cells(str(rango)) + ws.cell(row=row, column=1).value = None +tope = primera_nota if primera_nota else ws.max_row + 1 + +aplicado = set() +for row in range(5, tope): + nombre = str(ws.cell(row=row, column=1).value or "").strip() + if not nombre: + continue + for prefijo, (trab, pct) in ENCABEZADOS.items(): + if nombre.startswith(prefijo): + c = ws.cell(row=row, column=C_TRAB, value=round(trab, 2)) + c.number_format = "0.00" + p = ws.cell(row=row, column=C_PCT, value=pct) + p.number_format = "0%" + pinta(p, V_FILL if pct >= 1 else A_FILL, V_FONT if pct >= 1 else A_FONT, + bold=True, size=10, align="center") + break + for prefijo, delta in DELTAS_TRAB.items(): + if nombre.startswith(prefijo): + actual = ws.cell(row=row, column=C_TRAB).value + actual = float(actual) if isinstance(actual, (int, float)) else 0.0 + c = ws.cell(row=row, column=C_TRAB, value=round(actual + delta, 2)) + c.number_format = "0.00" + aplicado.add(prefijo) + break + for prefijo, (pct, txt, fill, fcolor, bold) in FILAS.items(): + if nombre.startswith(prefijo): + p = ws.cell(row=row, column=C_PCT, value=pct) + p.number_format = "0%" + pinta(p, A_FILL, A_FONT, bold=True, size=10, align="center") + s = ws.cell(row=row, column=C_STAT, value=txt) + pinta(s, fill, fcolor, bold) + break + +faltan = set(DELTAS_TRAB) - aplicado +assert not faltan, f"Filas no encontradas en el Excel: {faltan}" + +notas = [ + ("✅ Completada · 🟡 En curso · 🔴 Pendiente/bloqueada · ⚪ Próxima · " + "(adelantada) = ejecutada antes de su fecha plan", True), + (f"Horas trabajadas al 16-sep: Etapa 0 {E0:.2f} h (cerrada y validada) · Etapa 1 {E1:.2f} h " + f"(rango 32–39) · Etapa 2 {E2:.2f} h · ajuste Jira {AJ:.2f} h (de 7–10 aprobadas) · " + f"Etapa 3 {E3:.2f} h · total {TOTAL:.2f} h. Las {desde_6ago:.2f} h posteriores al corte del 6-ago " + "están reconstruidas con evidencia (commits, correos, informes de reunión) y pendientes de " + "validar. Base de facturación a $600/h + IVA.", True), + ("Etapa 1: la capa de escritura quedó implementada (cotización y prefactura con dry-run, " + "confirmación humana y auditoría). Solo Azure y CI/CD siguen dependiendo de Balam; mientras " + "tanto la plataforma está publicada en https://paputec.mx/balam/ desde el 3-sep (BIND y Jira " + "en solo lectura fuera de sesión).", False), + ("Integración Jira → BIND: primera cotización real desde un ticket el 21-ago (folio 1026, tras " + "5 intentos por dos semánticas indocumentadas del API: Type=2 y VAT como tasa), tres tickets " + "cotizados en la sesión del 3-sep y cotización en lote desde el 7-sep. Rebasa las 7–10 h " + "aprobadas por las semánticas indocumentadas y las sesiones acompañadas (12, 21-ago, 3-sep).", False), + ("Etapa 2: la prefactura por API NO es posible con lo que BIND expone (26-ago: IsFiscalInvoice=true " + "timbra al crear —CFDI 2386, a cancelar— y false produce remisión; el tipo Prefactura solo " + "existe en su UI). Sesiones del 9 y 10-sep con Araceli y Pedro para capturar el endpoint " + "interno y decidir el camino (réplica, detección al timbrado o soporte de BIND).", False), + ("En espera de Balam: acceso a Azure (requisitos enviados el 25-ago), cancelación del CFDI 2386 " + "y de la cotización 1026, y la definición del camino de prefactura.", False), +] +r = primera_nota if primera_nota else ws.max_row + 2 +for txt, bold in notas: + 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") + try: + ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=7) + except ValueError: + pass + ws.row_dimensions[r].height = 40 if len(txt) > 220 else (28 if len(txt) > 120 else 14) + r += 1 + +ws.print_area = f"A1:G{r}" +wb.save(DST) +print("OK ->", DST) +print(f" E0 {E0:.2f} · E1 {E1:.2f} · E2 {E2:.2f} · Jira {AJ:.2f} · E3 {E3:.2f} · TOTAL {TOTAL:.2f} h " + f"({nuevas:.2f} nuevas) · avance global {PCT_GLOBAL:.0%}")