Corte de horas al 1-ago (52.58 h): captura validada del 28-jul→1-ago, Excel de avance regenerado desde script (la versión enviada a Erika queda respaldada localmente en respaldos/)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Johann
2026-08-01 20:41:07 -06:00
parent 8d320edfa7
commit 7f86497314
4 changed files with 223 additions and 0 deletions
+214
View File
@@ -0,0 +1,214 @@
"""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%}")