diff --git a/planeacion/Avance-Balam-2026-07-28.xlsx b/planeacion/Avance-Balam-2026-07-28.xlsx index e81ae14..754c54f 100644 Binary files a/planeacion/Avance-Balam-2026-07-28.xlsx and b/planeacion/Avance-Balam-2026-07-28.xlsx differ diff --git a/planeacion/Avance-Balam-2026-08-01.xlsx b/planeacion/Avance-Balam-2026-08-01.xlsx new file mode 100644 index 0000000..a3fa280 Binary files /dev/null and b/planeacion/Avance-Balam-2026-08-01.xlsx differ diff --git a/planeacion/Seguimiento-horas.csv b/planeacion/Seguimiento-horas.csv index 61480a0..9a6e32d 100644 --- a/planeacion/Seguimiento-horas.csv +++ b/planeacion/Seguimiento-horas.csv @@ -34,3 +34,12 @@ fecha,semana,etapa,actividad_id,actividad,horas,estado_hora,fuente_evidencia,fac 2026-07-23,2026-W30,0,E0-07,Preparación del guion de validación del prototipo,0.45,validada,planeacion/Guion-validacion-prototipo-2026-07-23.md,si,"Guion del recorrido de la demo del 24-jul; ajustada a la baja por Johann 27-jul" 2026-07-24,2026-W30,0,E0-07,"Sesión de validación del prototipo Etapa 0 con Noé, Arturo y Araceli — APROBADO",1.05,confirmada,bitacora/REGISTRO.md#55,si,"Duración ~1 h 4 min según transcript; Etapa 0 validada, queda 1 definición de proceso (flujo Jira)" 2026-07-27,2026-W31,1,E1-07,"Sesión de API de Jira con Pedro (token PAF, bandeja FAC)",0.20,confirmada,bitacora/REGISTRO.md#56,si,~12 min; token PAF comprometido (1 año) por correo; integración inicia tras definición del flujo 28-jul +2026-07-28,2026-W31,1,E1-05,Requisitos de Azure comunicados a Erika (Contributor + Key Vault) y status de avance por WhatsApp,0.25,validada,bitacora/REGISTRO.md#59,si,validada por Johann 1-ago +2026-07-28,2026-W31,1,E1-08,"Propuesta v1.3 (ajuste Jira→BIND 7-10 h), Excel de etapas Avance-Balam-2026-07-28 y scripts del plan completo",2.50,validada,bitacora/REGISTRO.md#60,si,"commits ab7cc00..7502915; validada por Johann 1-ago" +2026-07-29,2026-W31,1,E1-05,"Envío de la v1.3 y el Excel por WhatsApp (8:18 am) y por correo (3:56 pm), coordinación con Erika",0.75,validada,bitacora/REGISTRO.md#59-60,si,validada por Johann 1-ago +2026-07-29,2026-W31,2,AJ-01,Primeras pruebas del token de Jira (401) y respuesta al correo de Pedro sobre la API,1.00,validada,planeacion/Investigacion-API-Jira.md,si,"Bloque 0; validada por Johann 1-ago" +2026-07-30,2026-W31,2,AJ-01,"Desbloqueo del acceso a la API de Jira (token regenerado → 200) e investigación completa (FAC, estatus, request types, límites)",3.00,validada,planeacion/Investigacion-API-Jira.md,si,"commit 980baf0; validada por Johann 1-ago" +2026-08-01,2026-W31,1,E1-05,Bitácora #59-#60 (aprobación de Noé) y actualización de pendientes,0.50,validada,bitacora/REGISTRO.md#59-60,si,"commit 2b862ee; validada por Johann 1-ago" +2026-08-01,2026-W31,1,E1-01,"B5 scheduler + B6 verificación RLS (rol balam_app) + B8 suite de integración (25 pruebas) + B9 seeder demo",1.50,confirmada,bitacora/REGISTRO.md#61,si,"Sesión asistida por IA; tiempo real de sesión" +2026-08-01,2026-W31,2,E2-01,"Frontend de lectura E2: login JWT, shell de marca, cartera por moneda, listado con filtros, detalle+PDF, clientes+CSV",1.00,confirmada,bitacora/REGISTRO.md#61,si,"Sesión asistida por IA; tiempo real de sesión" +2026-08-01,2026-W31,2,AJ-01,Cliente de lectura de la API de Jira (Balam.Integrations.Jira) con 10 pruebas y script de sondeo,0.50,confirmada,bitacora/REGISTRO.md#61,si,"Sesión asistida por IA; incluye prueba local nocturna" diff --git a/planeacion/generar_avance_2026_08_01.py b/planeacion/generar_avance_2026_08_01.py new file mode 100644 index 0000000..deccc52 --- /dev/null +++ b/planeacion/generar_avance_2026_08_01.py @@ -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%}")