159 lines
7.6 KiB
Python
159 lines
7.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Genera un XLSX con el resumen de sucursales con drift de custom fields
|
|
que fueron resueltas durante la sesión 2026-05-23."""
|
|
|
|
import datetime
|
|
import os
|
|
import sys
|
|
|
|
import openpyxl
|
|
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
|
|
|
|
|
|
HEADER_FONT = Font(bold=True, color="FFFFFF", size=11)
|
|
HEADER_FILL = PatternFill("solid", fgColor="305496")
|
|
THIN = Side(border_style="thin", color="B4B4B4")
|
|
BORDER = Border(top=THIN, bottom=THIN, left=THIN, right=THIN)
|
|
|
|
|
|
def style_header(ws, cols):
|
|
for cell in ws[1]:
|
|
cell.font = HEADER_FONT
|
|
cell.fill = HEADER_FILL
|
|
cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
|
|
cell.border = BORDER
|
|
ws.row_dimensions[1].height = 36
|
|
ws.freeze_panes = "A2"
|
|
for col_letter, width in cols.items():
|
|
ws.column_dimensions[col_letter].width = width
|
|
|
|
|
|
def style_body(ws):
|
|
for row in ws.iter_rows(min_row=2):
|
|
for cell in row:
|
|
cell.alignment = Alignment(vertical="top", wrap_text=True)
|
|
cell.border = BORDER
|
|
|
|
|
|
def main():
|
|
if hasattr(sys.stdout, "reconfigure"):
|
|
sys.stdout.reconfigure(encoding="utf-8")
|
|
|
|
wb = openpyxl.Workbook()
|
|
|
|
# === Sheet 1: Sucursales reales con problemas únicos ===
|
|
ws1 = wb.active
|
|
ws1.title = "Sucursales reales"
|
|
ws1.append([
|
|
"#", "Nombre Sucursal", "Location ID", "Categoría",
|
|
"Detalle del problema", "Resolución aplicada", "Records tocados",
|
|
])
|
|
style_header(ws1, {"A": 4, "B": 28, "C": 24, "D": 22, "E": 70, "F": 65, "G": 18})
|
|
|
|
sucursales = [
|
|
(1, "85960 - MP - Cd. Carmen", "XkduzafvwsrWcEFg6Qlj", "Canary inicial",
|
|
"6 fieldKeys divergentes (patrón estándar) + duplicado Fuente de Prospecto con 2 fieldKeys distintos.",
|
|
"Sucursal piloto de Fase 2. Migración masiva + force-delete del duplicado fuente_del_prospecto.",
|
|
"6 fields + 3 records"),
|
|
(2, "85935 - MP - Pilares", "uZnMH5bO6MXTHcgHeyq9", "Orphan con twin",
|
|
"Orphan opportunity.fuente_del_prospecto coexistía con twin clean opportunity.fuente_de_prospecto.",
|
|
"Force-delete del orphan vía migrate_branch_fieldkeys.py (twin tenía los valores replicados).",
|
|
"1 field"),
|
|
(3, "85966 - MP - Uruapan", "FoQWuksh4wQjPbVVZ8ZQ", "Conflicto case",
|
|
"Orphan contact.fuente_del_prospecto con 1 record (gabriela ramos), valor 'Sucursal' (title case) vs 'SUCURSAL' en twin.",
|
|
"Delete del orphan. Valor canónico ya existía en clean (mayúsculas, alineado con Marca).",
|
|
"1 field + 1 record"),
|
|
(4, "85931 - MP - Marina Nacional", "HvDw9Eg3rjrwkbQJXqfi", "Merge contactcontact*",
|
|
"4 orphans con doble prefijo contactcontact* (marca/año/version/modalidad del vehiculo), cada uno con 1 record único del contacto 'aide montes'. Plus campo regional legítimo 'Canciones'.",
|
|
"Merge orphan→clean para 'aide montes' (4 PUTs). Delete de 4 orphans. 'Canciones' borrado manualmente por el usuario.",
|
|
"5 fields + 4 records"),
|
|
(5, "85938 - MP - SENDERO", "UsHXqoj2l6ND7Uc7sEo2", "Missing field",
|
|
"Faltaba completamente opportunity.fuente_de_prospecto (no había ni clean ni divergent).",
|
|
"Field creado from-scratch copiando metadata (dataType SINGLE_OPTIONS + opciones) de Marca.",
|
|
"1 field creado"),
|
|
(6, "85976 - MP - Cancún", "uJEn2iuUficuml9zxAnt", "Caso complejo",
|
|
"Vehículo: 7 records con valores complementarios distintos en orphan vs clean. Modalidad: NO tenía clean target, había 2 fields divergentes (21+4 records con valores).",
|
|
"Vehículo: regenerar desde contact (marca+version+año) o concatenar donde contact vacío. Modalidad: crear clean from-scratch, 23 PUTs con prioridad contact>div>orphan, delete de los 2 divergentes.",
|
|
"2 fields + 4 records (Veh) + 1 field nuevo + 2 fields borrados + 23 records (Mod)"),
|
|
]
|
|
for row in sucursales:
|
|
ws1.append(row)
|
|
style_body(ws1)
|
|
|
|
# === Sheet 2: Cuentas DEMO ===
|
|
ws2 = wb.create_sheet("DEMOs")
|
|
ws2.append(["#", "Cuenta", "Location ID", "Problema", "Resolución"])
|
|
style_header(ws2, {"A": 4, "B": 28, "C": 24, "D": 70, "E": 65})
|
|
|
|
demos = [
|
|
(7, "Monte Providencia DEMO", "Vf7qQl3L9vakJ8hDtQ8e",
|
|
"Orphan NBSP _copy (CHECKBOX vacío). Campo de prueba 'Quoted Price' (TEXT). opportunity.canal_de_origen TEXT huérfano (el mismo que se borró en Marca al inicio). opportunity.opportunityvehiculo orphan doble prefijo. Display name desactualizado 'Fuente de Posible Cliente' vs Marca 'CANAL DE ORIGEN'.",
|
|
"NBSP + Quoted Price + canal_de_origen TEXT + opportunityvehiculo borrados (todos sin records con valor). Rename de Fuente de Posible Cliente → CANAL DE ORIGEN."),
|
|
(8, "0001 - MP - Qro DEMO", "Z64WQKORPVwXb5mn68Ef",
|
|
"4 orphans contactcontact* con valores únicos del contacto 'nombre test' (mismo patrón que Marina). Faltaba opportunity.fuente_de_prospecto (mismo caso que Sendero).",
|
|
"Merge 'nombre test' orphan→clean (4 PUTs + 4 deletes). Field creado from-scratch."),
|
|
]
|
|
for row in demos:
|
|
ws2.append(row)
|
|
style_body(ws2)
|
|
|
|
# === Sheet 3: Resumen ejecutivo ===
|
|
ws3 = wb.create_sheet("Resumen")
|
|
ws3.append(["Métrica", "Valor"])
|
|
style_header(ws3, {"A": 50, "B": 50})
|
|
resumen = [
|
|
("Audit inicial", "506 hallazgos"),
|
|
("Audit final", "0 hallazgos"),
|
|
("Reducción", "-100%"),
|
|
("Sucursales totales auditadas", "49 (47 producción + 2 DEMO)"),
|
|
("Sucursales reales con tratamiento individual", "6 (Cd. Carmen, Pilares, Uruapan, Marina Nacional, SENDERO, Cancún)"),
|
|
("Cuentas DEMO con problemas", "2 (Monte Providencia DEMO, 0001 Qro DEMO)"),
|
|
("", ""),
|
|
("Total fields migrados (mass migration)", "281 production + 2 DEMO = 283"),
|
|
("Total orphans eliminados", "52"),
|
|
("Total PUTs de merge orphan→clean", "35"),
|
|
("Total PUTs de regeneración canónica (Cancún)", "27"),
|
|
("Total fields creados from-scratch", "2 (Sendero, Qro DEMO opportunity.fuente_de_prospecto)"),
|
|
("Total renames cosméticos", "3"),
|
|
("Snapshots JSON generados", "Uno por cada operación destructiva, en /migrations/"),
|
|
("Data loss", "CERO"),
|
|
("", ""),
|
|
("Fecha sesión", datetime.datetime.now().strftime("%Y-%m-%d")),
|
|
]
|
|
for row in resumen:
|
|
ws3.append(row)
|
|
style_body(ws3)
|
|
for row in ws3.iter_rows(min_row=2):
|
|
row[0].font = Font(bold=True)
|
|
|
|
# === Sheet 4: Patrón base masivo ===
|
|
ws4 = wb.create_sheet("Patron base masivo")
|
|
ws4.append(["Nota"])
|
|
style_header(ws4, {"A": 100})
|
|
ws4.append([
|
|
"Las 48 sucursales MP completas tenían los 6 fieldKeys divergentes históricos "
|
|
"(cotizacin, cundo, visita_a_sucursal, _ltima_, atendi, de_empeo). Se resolvieron "
|
|
"con la migración masiva (281 fields) sin requerir tratamiento individual. Por eso no "
|
|
"aparecen en la pestaña principal — el problema era idéntico en todas y la solución "
|
|
"fue el mismo script (migrate_branch_fieldkeys.py)."
|
|
])
|
|
ws4["A2"].alignment = Alignment(vertical="top", wrap_text=True)
|
|
ws4.row_dimensions[2].height = 80
|
|
ws4["A2"].border = BORDER
|
|
|
|
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
if ROOT_DIR not in sys.path:
|
|
sys.path.insert(0, ROOT_DIR)
|
|
from paths import REPORT_DRIFT
|
|
os.makedirs(REPORT_DRIFT, exist_ok=True)
|
|
out_path = os.path.join(
|
|
REPORT_DRIFT,
|
|
f"sucursales_con_drift_resuelto_{datetime.datetime.now().strftime('%Y%m%d')}.xlsx",
|
|
)
|
|
wb.save(out_path)
|
|
print(f"XLSX generado: {out_path}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|