Actualiza cierre de Etapa 0 y control de horas

This commit is contained in:
JohannVelazquez
2026-07-16 09:44:21 -06:00
parent 8da11ea164
commit 3a66e5aee1
30 changed files with 1147 additions and 51 deletions
+288
View File
@@ -0,0 +1,288 @@
"""Actualiza el plan oficial con el corte reconstruido de horas al 15-jul-2026."""
from copy import copy
from pathlib import Path
import openpyxl
from openpyxl.styles import Alignment, Font, PatternFill
from openpyxl.utils import get_column_letter
PATH = Path(__file__).with_name("Plan-actividades-avance-2026-07-10-ajustado.xlsx")
def find_row(ws, task):
for row in range(2, ws.max_row + 1):
if ws.cell(row, 1).value == task:
return row
raise ValueError(f"No se encontró la tarea: {task}")
def copy_row_style(ws, source_row, target_row, max_col=11):
for col in range(1, max_col + 1):
source = ws.cell(source_row, col)
target = ws.cell(target_row, col)
if source.has_style:
target._style = copy(source._style)
target.number_format = source.number_format
target.alignment = copy(source.alignment)
workbook = openpyxl.load_workbook(PATH)
sheet = workbook["Plan"]
# Actividades separadas después del plan original. Las inserciones son idempotentes.
if not any(
sheet.cell(row, 1).value == "Definición de alcance Jira → cotización BIND"
for row in range(2, sheet.max_row + 1)
):
source_row = find_row(sheet, "Sesión de reglas de negocio con Arturo")
sheet.insert_rows(source_row + 1)
copy_row_style(sheet, source_row, source_row + 1)
values = [
"Definición de alcance Jira → cotización BIND",
"2 días",
"mié 22/07/26",
"jue 23/07/26",
None,
"Johan y Balam",
0,
]
for column, value in enumerate(values, start=1):
sheet.cell(source_row + 1, column, value)
if not any(
sheet.cell(row, 1).value == "CI/CD + gestión de secretos"
for row in range(2, sheet.max_row + 1)
):
source_row = find_row(sheet, "Repositorio + CI/CD + gestión de secretos")
sheet.insert_rows(source_row + 1)
copy_row_style(sheet, source_row, source_row + 1)
values = [
"CI/CD + gestión de secretos",
"1 día",
"vie 24/07/26",
"vie 24/07/26",
None,
"Johan y Pedro",
0,
]
for column, value in enumerate(values, start=1):
sheet.cell(source_row + 1, column, value)
# Calendario ajustado con los acuerdos del 1315 de julio.
updates = {
"Sesión de validación del prototipo": (
"1 día",
"jue 23/07/26",
"jue 23/07/26",
"Johan y Balam",
),
"Documento de hallazgos + ADRs + plan refinado": (
"10 días",
"vie 10/07/26",
"jue 23/07/26",
"Johan",
),
"Sesión de reglas de negocio con Arturo": (
"1 día",
"mié 22/07/26",
"mié 22/07/26",
"Arturo y CEO",
),
"Backend base: autenticación, roles y bitácora de auditoría": (
"5 días",
"lun 13/07/26",
"vie 17/07/26",
"Johan",
),
"Repositorio + CI/CD + gestión de secretos": (
"4 días",
"lun 13/07/26",
"jue 16/07/26",
"Pedro y Noé",
),
"Arquitectura multi-tenant (tenant_id + RLS)": (
"4 días",
"mar 14/07/26",
"vie 17/07/26",
"Johan",
),
}
for task, (duration, start, end, resources) in updates.items():
row = find_row(sheet, task)
sheet.cell(row, 2, duration)
sheet.cell(row, 3, start)
sheet.cell(row, 4, end)
sheet.cell(row, 6, resources)
rules_row = find_row(sheet, "Sesión de reglas de negocio con Arturo")
sheet.cell(rules_row, 1, "Sesión de reglas y dudas de Etapa 1")
repo_row = find_row(sheet, "Repositorio + CI/CD + gestión de secretos")
sheet.cell(repo_row, 1, "Repositorio privado GitHub")
headers = {
8: "Horas al 15/07",
9: "Tipo de registro",
10: "Notas de horas",
11: "Rango estimado",
}
for column, value in headers.items():
cell = sheet.cell(1, column, value)
cell.fill = copy(sheet.cell(1, 1).fill)
cell.font = copy(sheet.cell(1, 1).font)
cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
# Corte: 22 h de Etapa 0 + 8 h del inicio de Etapa 1 = 30 h.
hours = {
"Sesión de arranque (kickoff) con el Ing. Noé": (
2.00,
"Incluye preparación y sesión de 1 h",
),
"Sesión de Discovery: proceso actual de facturación y cobranza": (
5.50,
"Incluye preparación, sesiones y documentación de facturación/cobranza",
),
"Entrega y validación de accesos (BIND + manual de marca)": (
1.00,
"Coordinación, recepción y salvaguardas",
),
"Validación técnica de la API de BIND con la cuenta real": (
4.50,
"117 consultas GET, análisis y reporte técnico",
),
"Prototipo visual navegable": (
6.00,
"Diseño, construcción y ajustes con datos ficticios",
),
"Sesión de validación del prototipo": (0.00, "Agendada para el 23-jul"),
"Documento de hallazgos + ADRs + plan refinado": (
3.00,
"PDF/correo de entrega, hallazgos, ADRs y actualización del plan",
),
"Sesión de reglas y dudas de Etapa 1": (0.00, "Agendada para el 22-jul"),
"Definición de alcance Jira → cotización BIND": (
0.00,
"Se define con Balam el 2223 jul",
),
"Backend base: autenticación, roles y bitácora de auditoría": (
3.50,
"Trabajo local del 1315 jul",
),
"Repositorio privado GitHub": (0.50, "Coordinación de creación y acceso"),
"CI/CD + gestión de secretos": (0.00, "Programado para el 24-jul"),
"Arquitectura multi-tenant (tenant_id + RLS)": (
1.50,
"Diseño técnico inicial",
),
"Cliente de la API de BIND (reintentos, OData, errores)": (
2.00,
"Inicio del cliente productivo",
),
"Demostración semanal (viernes) + reporte de horas": (
0.50,
"Seguimiento, ajuste de calendario y corte de horas",
),
}
pending = {
"Sesión de validación del prototipo",
"Sesión de reglas y dudas de Etapa 1",
"Definición de alcance Jira → cotización BIND",
"CI/CD + gestión de secretos",
}
stage_rows = {0: [], 1: []}
current_stage = None
for row in range(2, sheet.max_row + 1):
task = sheet.cell(row, 1).value
if task == "Etapa 0":
current_stage = 0
sheet.cell(row, 11, "1822 h")
continue
if task == "Etapa 1":
current_stage = 1
sheet.cell(row, 11, "3239 h")
continue
if task == "Integracion de plataformas Balam":
sheet.cell(row, 11, "112136 h")
continue
if task not in hours:
continue
value, note = hours[task]
sheet.cell(row, 8, value)
sheet.cell(row, 9, "Pendiente" if task in pending else "Reconstruida")
sheet.cell(row, 10, note)
if current_stage in stage_rows:
stage_rows[current_stage].append(row)
project_row = find_row(sheet, "Integracion de plataformas Balam")
stage0_row = find_row(sheet, "Etapa 0")
stage1_row = find_row(sheet, "Etapa 1")
sheet.cell(stage0_row, 8, f"=SUM({','.join(f'H{row}' for row in stage_rows[0])})")
sheet.cell(stage1_row, 8, f"=SUM({','.join(f'H{row}' for row in stage_rows[1])})")
sheet.cell(project_row, 8, f"=H{stage0_row}+H{stage1_row}")
summaries = {
project_row: "Corte reconstruido al 15-jul",
stage0_row: "22 h: límite superior estimado de la etapa",
stage1_row: "8 h consumidas al 15-jul; etapa aún en curso",
}
for row, note in summaries.items():
sheet.cell(row, 9, "Resumen")
sheet.cell(row, 10, note)
for row in range(2, sheet.max_row + 1):
sheet.cell(row, 8).number_format = '0.00 "h"'
for column in range(8, 12):
sheet.cell(row, column).alignment = Alignment(vertical="top", wrap_text=True)
if sheet.cell(row, 1).value in {
"Integracion de plataformas Balam",
"Etapa 0",
"Etapa 1",
}:
for column in range(8, 12):
sheet.cell(row, column).font = Font(bold=True, color="FFFFFF")
sheet.cell(row, column).fill = PatternFill("solid", fgColor="331F0E")
widths = {
1: 58,
2: 13,
3: 16,
4: 16,
5: 14,
6: 22,
7: 14,
8: 16,
9: 18,
10: 55,
11: 18,
}
for column, width in widths.items():
sheet.column_dimensions[get_column_letter(column)].width = width
sheet.freeze_panes = "A2"
sheet.auto_filter.ref = f"A1:K{sheet.max_row}"
sheet.sheet_properties.pageSetUpPr.fitToPage = True
sheet.page_setup.fitToWidth = 1
sheet.page_setup.fitToHeight = 0
sheet.oddFooter.center.text = (
"Horas reconstruidas al 15-jul-2026; validar contra Jira antes de facturar"
)
stage0_total = sum(hours[sheet.cell(row, 1).value][0] for row in stage_rows[0])
stage1_total = sum(hours[sheet.cell(row, 1).value][0] for row in stage_rows[1])
assert stage0_total == 22.0
assert stage1_total == 8.0
temporary = PATH.with_suffix(".tmp.xlsx")
workbook.save(temporary)
check = openpyxl.load_workbook(temporary, data_only=False)["Plan"]
assert check.max_column == 11
assert check.cell(find_row(check, "Etapa 0"), 8).value.startswith("=SUM(")
assert check.cell(find_row(check, "Etapa 1"), 8).value.startswith("=SUM(")
temporary.replace(PATH)
print(f"Actualizado: {PATH}")
print("Etapa 0: 22.00 h | Etapa 1: 8.00 h | Total al 15-jul: 30.00 h")