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 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
@@ -77,3 +77,4 @@ fecha,semana,etapa,actividad_id,actividad,horas,estado_hora,fuente_evidencia,fac
|
|||||||
2026-09-09,2026-W37,2,E2-03,"Sesión 1:00 pm 'Seguimiento PAF - Prefactura' con Araceli y Pedro: prefactura creada en la UI de BIND ('Generar venta') mientras se capturan las peticiones (InvoiceDetails)",0.50,reconstruida,correo Read AI 9-sep 1:24 pm,si,"SIN registro en bitácora; por validar"
|
2026-09-09,2026-W37,2,E2-03,"Sesión 1:00 pm 'Seguimiento PAF - Prefactura' con Araceli y Pedro: prefactura creada en la UI de BIND ('Generar venta') mientras se capturan las peticiones (InvoiceDetails)",0.50,reconstruida,correo Read AI 9-sep 1:24 pm,si,"SIN registro en bitácora; por validar"
|
||||||
2026-09-10,2026-W37,2,E2-03,"Sesión 1:00 pm con Pedro: análisis de la cotización 1078 (Acuntia) y captura de la petición de red de la prefactura para evaluar su réplica",0.25,reconstruida,correo Read AI 10-sep 1:21 pm,si,"SIN registro en bitácora; por validar"
|
2026-09-10,2026-W37,2,E2-03,"Sesión 1:00 pm con Pedro: análisis de la cotización 1078 (Acuntia) y captura de la petición de red de la prefactura para evaluar su réplica",0.25,reconstruida,correo Read AI 10-sep 1:21 pm,si,"SIN registro en bitácora; por validar"
|
||||||
2026-09-15,2026-W38,1,E1-05,"Corte de horas 6-ago→15-sep (reconstrucción desde commits, correos e informes de reunión), Excel de avance, commits y publicación de ambos repositorios",0.75,reconstruida,planeacion/Seguimiento-horas.csv,si,"sesión asistida por IA; por validar"
|
2026-09-15,2026-W38,1,E1-05,"Corte de horas 6-ago→15-sep (reconstrucción desde commits, correos e informes de reunión), Excel de avance, commits y publicación de ambos repositorios",0.75,reconstruida,planeacion/Seguimiento-horas.csv,si,"sesión asistida por IA; por validar"
|
||||||
|
2026-09-16,2026-W38,1,E1-05,"Excel de avance publicado en Google Sheets (Drive/Balam vía rclone, enlace compartible) y corte del 16-sep regenerado desde script",0.50,reconstruida,planeacion/generar_avance_2026_09_16.py,si,"sesión asistida por IA; por validar"
|
||||||
|
|||||||
|
@@ -5,7 +5,7 @@ Fuente local de horas efectivamente trabajadas para el proyecto Balam. Se mantie
|
|||||||
**Tarifa contractual:** $600 MXN/h + IVA
|
**Tarifa contractual:** $600 MXN/h + IVA
|
||||||
**Cadencia:** corte semanal; factura los viernes; pago a 30 días
|
**Cadencia:** corte semanal; factura los viernes; pago a 30 días
|
||||||
**Datos tabulares:** [`Seguimiento-horas.csv`](Seguimiento-horas.csv)
|
**Datos tabulares:** [`Seguimiento-horas.csv`](Seguimiento-horas.csv)
|
||||||
**Última actualización:** 2026-09-15 (corte 6-ago → 15-sep reconstruido, por validar)
|
**Última actualización:** 2026-09-16 (corte 6-ago → 16-sep reconstruido, por validar)
|
||||||
|
|
||||||
## Reglas de captura
|
## Reglas de captura
|
||||||
|
|
||||||
@@ -18,18 +18,18 @@ Fuente local de horas efectivamente trabajadas para el proyecto Balam. Se mantie
|
|||||||
7. El esfuerzo real de una sesión incluye TODO su alrededor, no solo el desarrollo: el análisis/pensamiento previo para decidir qué se hace, la documentación de cada parte (bitácora, pendientes, investigación), y la comunicación asociada (cada correo, cada mensaje de WhatsApp, minutas y reportes). Definido por Johann el 6-ago.
|
7. El esfuerzo real de una sesión incluye TODO su alrededor, no solo el desarrollo: el análisis/pensamiento previo para decidir qué se hace, la documentación de cada parte (bitácora, pendientes, investigación), y la comunicación asociada (cada correo, cada mensaje de WhatsApp, minutas y reportes). Definido por Johann el 6-ago.
|
||||||
8. Para proyectar el costo final del proyecto, las horas no comprimibles (sesiones con Balam, pruebas acompañadas, capacitación, demos, gestión y comunicación) son el piso, y el objetivo de cierre es ese piso y un poco más — no el piso pelón ni el rango completo de la propuesta. Los cortes ya reportados a Erika nunca se recalculan retroactivamente (ajustado por Johann el 6-ago).
|
8. Para proyectar el costo final del proyecto, las horas no comprimibles (sesiones con Balam, pruebas acompañadas, capacitación, demos, gestión y comunicación) son el piso, y el objetivo de cierre es ese piso y un poco más — no el piso pelón ni el rango completo de la propuesta. Los cortes ya reportados a Erika nunca se recalculan retroactivamente (ajustado por Johann el 6-ago).
|
||||||
|
|
||||||
## Corte al 15-sep (reconstruido, por validar)
|
## Corte al 16-sep (reconstruido, por validar)
|
||||||
|
|
||||||
Del 10-ago al 15-sep no se capturó nada en el CSV; el 15-sep se reconstruyeron **39.25 h** con evidencia fechada (commits de ambos repos, correos, informes de reunión de Read AI y mtimes de archivos) y quedaron marcadas `reconstruida` (salvo la sesión del 12-ago, `confirmada` por transcript). **Johann debe validarlas** (o ajustarlas) antes de reportarlas; el corte del 6-ago (54.83 h) no se tocó.
|
Del 10-ago al 15-sep no se capturó nada en el CSV; el 15-sep se reconstruyeron **39.25 h** (más 0.50 h del 16-sep: Excel publicado en Google Sheets) con evidencia fechada (commits de ambos repos, correos, informes de reunión de Read AI y mtimes de archivos) y quedaron marcadas `reconstruida` (salvo la sesión del 12-ago, `confirmada` por transcript). **Johann debe validarlas** (o ajustarlas) antes de reportarlas; el corte del 6-ago (54.83 h) no se tocó.
|
||||||
|
|
||||||
| Etapa | Horas al corte | Rango | Estado |
|
| Etapa | Horas al corte | Rango | Estado |
|
||||||
|---|---:|---:|---|
|
|---|---:|---:|---|
|
||||||
| Etapa 0 | **21.83 h** | 18–22 h | Cerrada y validada |
|
| Etapa 0 | **21.83 h** | 18–22 h | Cerrada y validada |
|
||||||
| Etapa 1 | **32.25 h** | 32–39 h | Capa de escritura implementada; publicación provisional en paputec.mx (3-sep) |
|
| Etapa 1 | **32.75 h** | 32–39 h | Capa de escritura implementada; publicación provisional en paputec.mx (3-sep) |
|
||||||
| Etapa 2 (sin ajuste Jira) | **23.75 h** | 38–46 h | Cotización individual y en lote; prefactura por API bloqueada por BIND |
|
| Etapa 2 (sin ajuste Jira) | **23.75 h** | 38–46 h | Cotización individual y en lote; prefactura por API bloqueada por BIND |
|
||||||
| » Ajuste Jira → BIND | **14.75 h** | 7–10 h | ⚠️ Rebasado: semánticas indocumentadas del API y 5 sesiones acompañadas |
|
| » Ajuste Jira → BIND | **14.75 h** | 7–10 h | ⚠️ Rebasado: semánticas indocumentadas del API y 5 sesiones acompañadas |
|
||||||
| Etapa 3 | **1.50 h** | 24–29 h | Endurecimiento y runbook hechos en la publicación |
|
| Etapa 3 | **1.50 h** | 24–29 h | Endurecimiento y runbook hechos en la publicación |
|
||||||
| **Total** | **94.08 h** | | 39.25 h nuevas: 38.25 `reconstruida` + 1.00 `confirmada` (transcript del 12-ago) |
|
| **Total** | **94.58 h** | | 39.75 h nuevas desde el 6-ago: 38.75 `reconstruida` + 1.00 `confirmada` (transcript del 12-ago) |
|
||||||
|
|
||||||
**Criterios usados en la reconstrucción:**
|
**Criterios usados en la reconstrucción:**
|
||||||
- Sesiones con Balam: duración acotada por la hora de inicio de la convocatoria y la hora en que llegó el informe de Read AI (redondeada a 0.25 h). Sin transcript no se infló nada; Johann puede subirlas si duraron más.
|
- Sesiones con Balam: duración acotada por la hora de inicio de la convocatoria y la hora en que llegó el informe de Read AI (redondeada a 0.25 h). Sin transcript no se infló nada; Johann puede subirlas si duraron más.
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
"""Genera Avance-Balam-2026-09-16.xlsx a partir del corte del 15-sep.
|
||||||
|
|
||||||
|
Corte del miércoles 16-sep: sin sesiones ni desarrollo nuevos desde el 15-sep;
|
||||||
|
solo se publicó el Excel de avance en Google Sheets (Drive/Balam) y se
|
||||||
|
regeneró este corte. El corte del 15-sep no se toca. Las horas se leen de
|
||||||
|
`Seguimiento-horas.csv`; el script aborta si el reparto no cuadra con las
|
||||||
|
horas nuevas del 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-09-15.xlsx")
|
||||||
|
DST = os.path.join(BASE, "Avance-Balam-2026-09-16.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"
|
||||||
|
|
||||||
|
C_PCT, C_TRAB, C_STAT = 4, 6, 7
|
||||||
|
CORTE_ANTERIOR = 94.08 # corte del 15-sep
|
||||||
|
|
||||||
|
# --- CSV: totales por etapa y por actividad -----------------------------------
|
||||||
|
por_act, por_etapa = defaultdict(float), defaultdict(float)
|
||||||
|
nuevas, desde_6ago = 0.0, 0.0
|
||||||
|
with open(CSV_HORAS, encoding="utf-8", newline="") as fh:
|
||||||
|
for r in csv.DictReader(fh):
|
||||||
|
if not (r.get("fecha") or "").strip():
|
||||||
|
continue
|
||||||
|
h = float(r["horas"])
|
||||||
|
por_act[r["actividad_id"]] += h
|
||||||
|
por_etapa[r["etapa"]] += h
|
||||||
|
if r["fecha"] > "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%}")
|
||||||
Reference in New Issue
Block a user