Files
balam/planeacion/generar_avance_2026_08_01.py

215 lines
11 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 Avance-Balam-2026-08-01.xlsx a partir del canónico del 28-jul.
Corte del sábado 1-ago (REGISTRO #61): cierre técnico de la Etapa 1 en lo no
bloqueado (B5 scheduler, B6 RLS verificado, B8 suite de integración, B9 seeder
demo), frontend de LECTURA de la Etapa 2 completo (adelantando las filas del
29-jul al 5-ago y la exportación CSV) y cliente de lectura de la API de Jira.
La propuesta v1.3 fue APROBADA por Noé el 30-jul (REGISTRO #60).
Las horas se leen de `Seguimiento-horas.csv` (fuente de la verdad, corte
52.58 h) y se reparten por fila con el mismo criterio trazable del generador
del 27-jul; el script aborta si el reparto no cuadra con el CSV.
"""
import csv
import os
from collections import defaultdict
import openpyxl
from openpyxl.styles import Alignment, Font, PatternFill
BASE = os.path.dirname(os.path.abspath(__file__))
SRC = os.path.join(BASE, "Avance-Balam-2026-07-28.xlsx")
DST = os.path.join(BASE, "Avance-Balam-2026-08-01.xlsx")
CSV_HORAS = os.path.join(BASE, "Seguimiento-horas.csv")
CAFE, GRIS = "331F0E", "595959"
V_FILL, V_FONT = "C6EFCE", "006100"
A_FILL, A_FONT = "FFEB9C", "9C6500"
R_FILL, R_FONT = "FFC7CE", "9C0006"
G_FILL, G_FONT = "F2F2F2", "595959"
C_PCT, C_EST, C_TRAB, C_STAT = 4, 5, 6, 7
# --- CSV: totales por etapa y por actividad -----------------------------------
por_act, por_etapa = defaultdict(float), defaultdict(float)
with open(CSV_HORAS, encoding="utf-8", newline="") as fh:
for r in csv.DictReader(fh):
if not (r.get("fecha") or "").strip():
continue
por_act[r["actividad_id"]] += float(r["horas"])
por_etapa[r["etapa"]] += float(r["horas"])
E0, E1 = por_etapa["0"], por_etapa["1"]
AJ = por_act["AJ-01"] # ajuste Jira→BIND (vive dentro de la etapa 2 del CSV)
E2 = por_etapa["2"] - AJ # Etapa 2 propiamente (frontend de lectura)
TOTAL = E0 + E1 + E2 + AJ
assert abs(TOTAL - sum(por_etapa.values())) < 0.01
PCT_GLOBAL = round(TOTAL / ((119 + 146) / 2), 2)
# --- Reparto por fila del delta 28-jul→1-ago (11.00 h nuevas) ------------------
# Trazabilidad (mismas reglas que el generador del 27-jul):
# E1-05 +1.50 (Azure/status 0.25 + envíos v1.3 0.75 + bitácora 0.50) → fila de seguimiento semanal
# E1-08 +2.50 (propuesta v1.3 + Excel de etapas + plan completo) → fila "Definición de alcance Jira"
# E1-01 +1.50 del 1-ago repartida por naturaleza: B5+B8 → backend; B6 → multi-tenant; B9 → modelo
# AJ-01 4.50 (pruebas 401 + investigación + cliente de lectura) → fila "Cliente de la API de Jira"
# E2-01 1.00 repartida entre las filas de frontend que cubrió la sesión
DELTAS_TRAB = {
"Demostración semanal": 1.50,
"Definición de alcance Jira": 2.50,
"Backend base": 0.50,
"Arquitectura multi-tenant": 0.50,
"Modelo de facturas": 0.50,
"Cliente de la API de Jira": 4.50,
"Andamiaje del frontend": 0.30,
"Layout base": 0.15,
"Listado de facturas": 0.25,
"Detalle de factura": 0.15,
"Catálogo de clientes": 0.10,
"Exportación de facturas": 0.05,
}
assert abs(sum(DELTAS_TRAB.values()) - 11.00) < 0.01, "El reparto no suma las 11.00 h nuevas"
# Encabezados de etapa: trab y % (Etapa 1: 7 de 8 entregables terminados — solo
# queda la capa de escritura, bloqueada por Balam; % por entregable cerrado).
ENCABEZADOS = {
"Integracion de plataformas Balam": (TOTAL, PCT_GLOBAL),
"Etapa 0": (E0, 1.0),
"Etapa 1": (E1, 0.875),
"Etapa 2": (E2, 0.50),
"» Ajuste dentro de la Etapa 2": (AJ, 0.45),
"Etapa 3": (None, 0),
}
# (pct, estatus, fill, font, bold) por prefijo de fila — refleja el 1-ago.
FILAS = {
"Modelo de facturas": (1.0, "✅ Completada (modelo, migraciones y seeder de datos demo)", V_FILL, V_FONT, False),
"Demostración semanal": (None, "🟡 En curso (semanal) · demo del vie 7-ago con material listo", A_FILL, A_FONT, False),
"Definición de alcance Jira": (0.95, "🟡 v1.3 APROBADA por Noé (30-jul) · falta el estatus disparador", A_FILL, A_FONT, False),
"Capa de escritura controlada": (0.40, "🔴 Diseño listo · impl. en pausa (flujo en revisión interna de Balam)", R_FILL, R_FONT, True),
"Configuración de infraestructura en Azure": (0, "🔴 Bloqueada · acceso Contributor escalado a Noé (28-jul)", R_FILL, R_FONT, True),
"CI/CD": (0, "🔴 Pendiente · depende de Azure", R_FILL, R_FONT, True),
# Ajuste Jira
"Cliente de la API de Jira": (0.85, "🟡 Cliente de lectura listo y probado (1-ago) · faltan muestreos en FAC", A_FILL, A_FONT, False),
"Disparador por estatus": (0, "🔴 Bloqueada · decisiones de Balam (request type, estatus, monto/conceptos)", R_FILL, R_FONT, True),
"Trazabilidad ticket↔cotización↔factura": (0, "🔴 Bloqueada · requiere el disparador y sesión de escritura acompañada", R_FILL, R_FONT, True),
# Etapa 2 (lectura adelantada el 1-ago)
"Andamiaje del frontend": (1.0, "✅ Completada (1-ago)", V_FILL, V_FONT, False),
"Layout base": (1.0, "✅ Completada (1-ago)", V_FILL, V_FONT, False),
"Listado de facturas": (1.0, "✅ Completada (1-ago, adelantada) · filtros y paginación en servidor", V_FILL, V_FONT, False),
"Detalle de factura": (0.8, "🟡 PDF listo (1-ago, adelantada) · XML pendiente de confirmar con BIND", A_FILL, A_FONT, False),
"Catálogo de clientes": (0.7, "🟡 Catálogo y búsqueda listos (adelantada) · lista blanca editable pendiente", A_FILL, A_FONT, False),
"Tipo de cambio del DOF": (0, "⚪ Próxima · la captura al facturar depende del flujo definido", G_FILL, G_FONT, False),
"Exportación de facturas": (0.8, "🟡 CSV listo (adelantada) · XLSX pendiente", 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 1-ago-2026 · horas trabajadas al corte del sábado 1-ago · "
"frontend de consulta de la Etapa 2 adelantado; escritura y Azure en espera de Balam")
ws["A2"].font = Font(size=10, italic=True, color=GRIS)
aplicado = set()
for row in range(5, ws.max_row + 1):
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):
if trab is not None:
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%"
if pct >= 1:
pinta(p, V_FILL, V_FONT, bold=True, size=10, align="center")
elif pct > 0:
pinta(p, A_FILL, 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):
if pct is not None:
p = ws.cell(row=row, column=C_PCT, value=pct)
p.number_format = "0%"
if pct >= 1:
pinta(p, V_FILL, V_FONT, bold=True, size=10, align="center")
elif pct > 0:
pinta(p, A_FILL, A_FONT, bold=True, size=10, align="center")
else:
p.font = Font(size=10, color=GRIS)
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: se reemplaza el bloque del 28-jul ---------------------------------
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:
ws.cell(row=row, column=1).value = None
notas = [
("✅ Completada · 🟡 En curso · 🔴 Pendiente/bloqueada · ⚪ Próxima · "
"(adelantada) = ejecutada antes de su fecha plan", True),
(f"Horas trabajadas al corte del sábado 1-ago: Etapa 0 {E0:.2f} h (cerrada y validada) · "
f"Etapa 1 {E1:.2f} h (rango 32–39) · Etapa 2 {E2:.2f} h · ajuste Jira {AJ:.2f} h "
f"(de 7–10 aprobadas) · total {TOTAL:.2f} h. Base de facturación a $600/h + IVA.", True),
("Etapa 1: 7 de 8 entregables contractuales terminados. El 1-ago se cerraron el "
"seeder de datos de demostración, la verificación real del aislamiento RLS, el "
"sincronizador automático (cada 15 min) y la suite de pruebas de integración "
"(61 pruebas). Solo queda la capa de escritura, en pausa por la definición del "
"flujo de facturación.", False),
("Etapa 2: el frontend de CONSULTA quedó adelantado al 1-ago (inicio de sesión por "
"roles, cartera por moneda, listado de facturas con filtros/búsqueda/paginación, "
"detalle con descarga del PDF del CFDI y catálogo de clientes con exportación). "
"Las filas de emisión (cotizaciones y conversión) siguen sujetas al flujo definido.", False),
("Integración Jira → BIND (7–10 h, v1.3 aprobada por el Ing. Noé el 30-jul): el "
"cliente técnico de lectura quedó construido y probado; el disparador y la "
"trazabilidad esperan las definiciones de negocio (request type, estatus "
"disparador y origen de monto/conceptos) y la prueba de escritura acompañada.", False),
("En espera de Balam: acceso a Azure (grupo de recursos con Contributor — con el "
"Ing. Noé desde el 28-jul; bloquea ambiente, staging y CI/CD), fecha de la sesión "
"del flujo de facturación, y las tres definiciones de la integración Jira.", 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 # ya estaba fusionada desde el archivo origen
ws.row_dimensions[r].height = 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} · TOTAL {TOTAL:.2f} h "
f"· avance global {PCT_GLOBAL:.0%}")