Files
balam/planeacion/actualizar_excel_horas.py
T
2026-07-16 09:44:21 -06:00

289 lines
9.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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")