From 6d4b7814864f8704b135f5128b66aaf74c293c69 Mon Sep 17 00:00:00 2001 From: Johann Date: Thu, 6 Aug 2026 14:17:06 -0600 Subject: [PATCH] =?UTF-8?q?2.=C2=BA=20corte=20del=206-ago=20(54.83=20h):?= =?UTF-8?q?=20wizard=20de=20emisi=C3=B3n=20asistida=20construido=20en=20si?= =?UTF-8?q?mulaci=C3=B3n=20(1.00=20h=20de=20sesi=C3=B3n=20vespertina)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit El wizard de 5 pasos de la VISTA 3 quedó navegable en balam-plataforma con datos reales de solo lectura (cotizaciones de /api/quotes y ticket FAC vivo): selección de cotización, datos de factura con regla PPD/PUE y advertencia SAT, IVA por regla con totales reactivos, prefactura simulada con marca de agua y aprobación humana visual; el timbrado queda deshabilitado hasta B7 (commits 2164f6c, 90f7633 y 0600f2f en el repo del cliente). Excel de avance del 2.º corte generado desde script (el corte matutino no se toca); la fila de conversión cotización→factura pasa a 40% como interfaz en simulación. --- planeacion/Avance-Balam-2026-08-06b.xlsx | Bin 0 -> 10405 bytes planeacion/Seguimiento-horas.csv | 1 + planeacion/generar_avance_2026_08_06b.py | 192 +++++++++++++++++++++++ 3 files changed, 193 insertions(+) create mode 100644 planeacion/Avance-Balam-2026-08-06b.xlsx create mode 100644 planeacion/generar_avance_2026_08_06b.py diff --git a/planeacion/Avance-Balam-2026-08-06b.xlsx b/planeacion/Avance-Balam-2026-08-06b.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..59327fab5fa1186ea6928e5f020252acb4002db5 GIT binary patch literal 10405 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 F{{vymEBXKc literal 0 HcmV?d00001 diff --git a/planeacion/Seguimiento-horas.csv b/planeacion/Seguimiento-horas.csv index 6148c77..cafae99 100644 --- a/planeacion/Seguimiento-horas.csv +++ b/planeacion/Seguimiento-horas.csv @@ -46,3 +46,4 @@ fecha,semana,etapa,actividad_id,actividad,horas,estado_hora,fuente_evidencia,fac 2026-08-06,2026-W32,2,AJ-01,"Sondeo de customfields en FAC (IDs capturados y documentados), token a user-secrets y endpoints de lectura /api/jira/solicitudes (+converter de fechas de Jira)",0.50,confirmada,planeacion/Investigacion-API-Jira.md,si,"Sesión asistida por IA; tiempo real de sesión; commits 4ce5640 (privado) y e2c2d18 (cliente)" 2026-08-06,2026-W32,2,AJ-01,"Bandeja de solicitudes y detalle de ticket fieles al prototipo leyendo FAC en vivo (estatus reales, solo lectura) + adjuntos descargables vía proxy",0.50,confirmada,bitacora/PENDIENTES.md,si,"Sesión asistida por IA; tiempo real de sesión; commits e2c2d18 y 76ad7b1 (cliente)" 2026-08-06,2026-W32,2,E2-01,"Shell con el chrome del prototipo aplicado a toda la app (sidebar con grupos y logo oficial, breadcrumb, chips de sync y módulos próximamente)",0.25,confirmada,prototipo/Prototipo-Etapa0.html,si,"Sesión asistida por IA; tiempo real de sesión; commit e2c2d18 (cliente)" +2026-08-06,2026-W32,2,E2-01,"Wizard de emisión asistida (5 pasos, solo UI) fiel al prototipo: cotizaciones BIND reales seleccionables, datos de factura con PPD/PUE y alerta SAT, IVA por regla con totales reactivos, prefactura simulada con marca de agua y aprobación humana visual; timbrado bloqueado hasta B7",1.00,confirmada,prototipo/Prototipo-Etapa0.html,si,"Sesión asistida por IA; tiempo real de sesión; commits 2164f6c, 90f7633 y 0600f2f (cliente)" diff --git a/planeacion/generar_avance_2026_08_06b.py b/planeacion/generar_avance_2026_08_06b.py new file mode 100644 index 0000000..feb7983 --- /dev/null +++ b/planeacion/generar_avance_2026_08_06b.py @@ -0,0 +1,192 @@ +"""Genera Avance-Balam-2026-08-06b.xlsx a partir del corte de la tarde del 6-ago. + +Segundo corte del jueves 6-ago (sesión vespertina, previa a la demo del vie +7-ago): el wizard de emisión asistida quedó construido en la plataforma como +interfaz navegable de simulación — 5 pasos fieles al prototipo aprobado, con +las cotizaciones reales de BIND y el ticket FAC vivo (solo lectura), prefactura +simulada con marca de agua y aprobación humana visual. El timbrado queda +deshabilitado hasta que Balam autorice la escritura (B7). El corte de la +mañana (Avance-Balam-2026-08-06.xlsx) no se toca. + +Las horas se leen de `Seguimiento-horas.csv` (fuente de la verdad, corte +54.83 h); el script aborta si el reparto por fila 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-08-06.xlsx") +DST = os.path.join(BASE, "Avance-Balam-2026-08-06b.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) +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 corte matutino→vespertino (1.00 h nueva) ------- +# Trazabilidad: +# E2-01 +1.00 (wizard de emisión asistida, 5 pasos en simulación) +# → fila "Conversión cotización→factura" (el wizard ES su interfaz; +# la conversión real sigue bloqueada por B7) +DELTAS_TRAB = { + "Conversión cotización→factura": 1.00, +} +assert abs(sum(DELTAS_TRAB.values()) - 1.00) < 0.01, "El reparto no suma la 1.00 h nueva" + +ENCABEZADOS = { + "Integracion de plataformas Balam": (TOTAL, PCT_GLOBAL), + "Etapa 0": (E0, 1.0), + "Etapa 1": (E1, 0.875), + "Etapa 2": (E2, 0.55), + "» Ajuste dentro de la Etapa 2": (AJ, 0.55), + "Etapa 3": (None, 0), +} + +# (pct, estatus, fill, font, bold) — solo lo que cambió respecto al corte matutino. +FILAS = { + "Conversión cotización→factura": (0.4, "🟡 Interfaz completa en simulación (6-ago): wizard de 5 pasos con " + "cotizaciones y tickets reales, prefactura simulada y aprobación humana " + "visual · el timbrado real espera la autorización de escritura (B7)", + 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 6-ago-2026 (2.º corte del día) · lectura de la bandeja Jira (FAC) " + "completa y wizard de emisión asistida en simulación; timbrado y escritura en espera de Balam") +ws["A2"].font = Font(size=10, italic=True, color=GRIS) + +# Las notas del corte anterior van fusionadas A:G y algunas empiezan con +# "Etapa 1: …", que colisiona con los prefijos de encabezado: se localizan y +# limpian ANTES del bucle principal, y este se detiene donde empezaban. +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): + 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 escribe el bloque vespertino donde estaba el matutino ---------- +notas = [ + ("✅ Completada · 🟡 En curso · 🔴 Pendiente/bloqueada · ⚪ Próxima · " + "(adelantada) = ejecutada antes de su fecha plan", True), + (f"Horas trabajadas al 2.º corte del jueves 6-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. Solo queda la capa de " + "escritura, en pausa por la definición del flujo de facturación.", False), + ("Integración Jira → BIND (7–10 h, v1.3 aprobada por el Ing. Noé el 30-jul): la " + "LECTURA quedó completa el 6-ago — la plataforma muestra la bandeja de solicitudes " + "y el detalle de cada ticket de FAC en vivo (solo lectura), con los campos del " + "formulario mapeados y los adjuntos descargables. El disparador de cotización 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), + ("Etapa 2: al frontend de consulta y al chrome del prototipo se sumó el wizard de " + "emisión asistida (6-ago): 5 pasos sobre cotizaciones y tickets reales, con regla " + "PPD/PUE y advertencia SAT, IVA automático, prefactura simulada y aprobación humana " + "visual. El timbrado queda deshabilitado hasta que Balam autorice la escritura (B7).", False), + ("En espera de Balam: acceso a Azure (Contributor — con el Ing. Noé desde el " + "28-jul), 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%}")