From a731779958aad7ea00823b2694701b64b89bfabe Mon Sep 17 00:00:00 2001 From: Administrator Date: Mon, 20 Jul 2026 13:40:36 -0500 Subject: [PATCH] Subir archivos a "/" --- FUTUROS_CAMBIOS.md | 173 ++++++++++++++++++++++++++++++++++++++++++ INICIAR_LEADS.bat | 12 +++ OPTIMIZACION_CACHE.md | 78 +++++++++++++++++++ _run_backend.bat | 11 +++ _run_frontend.bat | 12 +++ 5 files changed, 286 insertions(+) create mode 100644 FUTUROS_CAMBIOS.md create mode 100644 INICIAR_LEADS.bat create mode 100644 OPTIMIZACION_CACHE.md create mode 100644 _run_backend.bat create mode 100644 _run_frontend.bat diff --git a/FUTUROS_CAMBIOS.md b/FUTUROS_CAMBIOS.md new file mode 100644 index 0000000..4a4dd82 --- /dev/null +++ b/FUTUROS_CAMBIOS.md @@ -0,0 +1,173 @@ +# FUTUROS CAMBIOS — Dashboard LEADS + +Lista de cosas pendientes que el usuario quiere hacer más adelante. +Cuando el usuario pida "futuros cambios", recordarle TODO esto. + +--- + +## 1. Mapa de campañas (frases de pauta) → mover a GitHub o Supabase +- Hoy está incrustado en el código: `backend/data_manager_v2.py`, lista `CAMPANIAS` + (las ~90 frases con emojis + programa/sede/código/rango de fechas). +- Objetivo: sacarlo a un JSON en GitHub (o tabla en Supabase) para editarlo + sin tocar el código (igual que se hace en el otro dashboard con las clasificaciones). + +## 2. Leyenda de los códigos de pauta → a GitHub/Supabase +- Los códigos de campaña (191, 193, 63a, etc.) y su significado. +- Mismo objetivo: que sea editable fuera del código. + +## 3. Excluir cursos (num_indice) puntuales del cálculo +- Por ahora EXCLUIR del cálculo de Ocupabilidad/cursos: **num_indice 1154 y 1121**. +- Motivo: el usuario indicó que no deben contar (ej. reprogramados a otro mes). +- Idea a futuro: manejar esta lista de exclusión desde GitHub/Supabase, no en código. +- (Relacionado: el PBI filtra cursos por FECHA_MOSTRAR_COL = fecha con reprogramación + de SharePoint; aquí no tenemos esa columna todavía → Cursos Reprogramados quedó en fase 2.) + +## 4. Reprogramación de cursos (SharePoint) — FASE 2 [DEPENDE DE LA FUENTE] +La fecha ORIGINAL planificada de cada curso venía de SharePoint +(BaseBI_Programacion: FECHA CALENDARIO vs FECHA REPROGRAMADO). Sin esa fuente, +varios KPIs no se pueden calcular exacto. Pendiente: traerla de Google Sheets / Supabase / JSON +y construir FECHA_MOSTRAR_COL, FECHA_CUADRO_BI y ESTADO_CUADRO_BI. + +KPIs/cosas que dependen de esto (hoy aproximados o en 0): +- **Cursos Reprogramados** → hoy SALE 0 (no hay fecha original para comparar). +- **Cursos Programados** → hoy usa solo `fch_inicio` (el PBI usa planificada + reprogramada). + Diferencia chica, se cuadrará en fase 2. +- **Cursos Iniciados / Suspendidos** → hoy por `fch_inicio` y `cod_estado`; revisar con reprogramación. +- La exclusión manual de cursos (1154, 1121) es justo por reprogramación: cuando llegue + la fuente, esto debería resolverse solo (ya no haría falta la lista manual). + +## 5. Matriz por Curso (num_indice) con métricas de Leads por Pauta — NUEVA +El usuario quiere una MATRIZ debajo de la tabla por Sede, con: +- FILA: Fact_SQL_Base_Cursos[Personalizado], separada por cada num_indice (cursos se repiten). +- VALORES: Fecha_Inicio_Visual, Leads_Acumulados_Historico, Cant_Leads_Nuevos, + Cant_Leads_Nuevos_Con_Asesor, Cant_Leads_Nuevo_x_Mes, Cant_Leads_Nuevos_x_Mes_Con_Asesor. + +DEPENDE DE: el campo **Pauta** (código de campaña) de CADA curso, que en el PBI venía de +SharePoint (BaseBI_Programacion → PAUTA_CODIGO). El cruce de TODAS esas medidas es por +TREATAS(Cursos[Pauta], Leads[codigo]) — o sea por CÓDIGO, no por teléfono. + +Lo que se necesita para construirla (traer a Google Sheets / Supabase / JSON): +- Por cada num_indice (curso): su **código(s) de Pauta** asociado. +- Idealmente también FECHA_CUADRO_BI (fecha real/reprogramada) — mismo paquete que el punto 4. + +Medidas (ya tengo las fórmulas DAX exactas guardadas): +- Cant_Leads_Nuevos = leads únicos (Cantidad_Veces=1) con codigo = Pauta del curso, histórico. +- ..._Con_Asesor = + asesor no vacío. +- ..._x_Mes = igual pero respeta filtro de fecha del mes. +- ..._x_Mes_Con_Asesor = del mes + con asesor. +- Leads_Acumulados_Historico = suma de Cant_Leads_Nuevos de cursos del mismo Personalizado + con fecha <= la del curso actual. +- Fecha_Inicio_Visual = FECHA_CUADRO_BI del curso. + +ESTADO: ✅ La MATRIZ YA ESTÁ CONSTRUIDA en el dashboard (backend `leads_logic.matriz_cursos`, +frontend "Detalle por Curso"). Hoy muestra Programa + Fecha Inicio reales; las 5 columnas de +leads salen en 0 porque FALTA el código de Pauta por curso. Cuando llegue la fuente, solo se +rellena el cruce y se activan las 5 medidas. + +### Fuente SharePoint que hay que conectar (acordado) +Archivo: Base_BI_2.xlsx +Ruta: https://escuelarefrigeracion.sharepoint.com/sites/ASESORASCOMERCIALES/ + Documentos compartidos/2. BASE PROSPECTOS/BASE GENERAL/Patricia/Base_BI_2.xlsx +Tabla: BaseBI_Programacion +Columnas: NUM_INDICE, FECHA CALENDARIO, FECHA REPROGRAMADO, ESTADO, CONTAR, PAUTA_CODIGO +Cruce: Cursos[NUM_INDICE] → BaseBI_Programacion[NUM_INDICE] ⇒ PAUTA_CODIGO + (en el PBI: columna calculada Pauta = RELATED(BaseBI_Programacion[PAUTA_CODIGO])) + Luego: TREATAS(Cursos[Pauta], Leads[codigo]) para contar leads. + +Cómo leerla (3 opciones, de menos a más esfuerzo): +1) (Recomendado) Que Patricia/usuario suba a Supabase una tabla `cursos_pauta` + (num_indice, pauta_codigo, fecha_cuadro_bi). El backend ya tiene credenciales Supabase. + → cero auth de Microsoft, instantáneo, editable. +2) Exportar esa hoja a un Google Sheet / CSV en GitHub (GITHUB_BASE ya configurado). +3) Lectura directa SharePoint con Office365-REST-Python-Client (requiere usuario+clave M365 + o app registration). Más frágil; dejarlo como última opción. + +Cuando exista la fuente, en `data_manager_v2.py` agregar `traer_cursos_pauta()` y mapear +num_indice→pauta sobre los cursos; en `leads_logic.matriz_cursos` reemplazar los 0 por el +conteo de leads cruzado por codigo (las fórmulas DAX de arriba ya están claras). + +## 6. Base Junta (identificador de canal de origen por teléfono) — FUTURO +Objetivo: cuando un número se MATRICULA, saber su CANAL DE ORIGEN (y sede/programa/código) +para clasificarlo. Se usará como "memoria" de consulta por teléfono. + +Fuente/lógica (ya PROBADA y validada con el usuario): +- Une leads de POSTGRE (Chatwoot + mapa de campañas CAMPANIAS → canal/sede/programa/código) + + SUPABASE `datos_unificados` (Telefono, Fechacreada, Canal, Sede, Programa, Codigo). +- Teléfono NORMALIZADO: quita '+51', '+', espacios, '-', '(', ')'. +- DEDUP por teléfono → se queda con el MÁS ANTIGUO (solo por FECHA, sin hora). +- Desempate misma fecha: 1) PAUTA_WSP 2) PAUTA_WSP_FACE 3) orden alfabético del canal. +- Texto en MAYÚSCULAS; '-' de Supabase → vacío. +- Resultado prueba: ~45,054 teléfonos únicos. + +Script de prueba (standalone, corre en cualquier carpeta): `base_junta_full.py` +(genera base_junta_full.xlsx con 6 columnas: Telefono, Canal, Fecha Creada, Sede, +Programa, Codigo). NO está conectado al dashboard todavía. + +Cómo conectarlo a futuro (acordado, debe ser LIVIANO): +- Subir la base junta a una tabla en Supabase (ej. `base_junta`). +- El backend la carga UNA vez en un dict {telefono → datos}, cacheado (refresco ~15 min). +- Al cruzar una matrícula por teléfono → se obtiene su canal de origen y se clasifica. +- 44-73k filas en dict = instantáneo, sin impacto en la carga. + +ESTADO: pendiente. Cuando se retome: crear tabla Supabase + traer_base_junta() en backend ++ cruce por teléfono en el módulo que corresponda. + +## 7. Simplificar/normalizar nombres de programa (Personalizado) — MEJORA OPCIONAL +Los nombres largos de los cursos se acortan con una lista de reemplazos de texto +en `backend/leads_logic.py` → `_PERS_REEMPLAZOS` (hardcoded). + +Problemas/mejoras: +- Está en el código: cada nombre nuevo/largo hay que agregarlo a mano. +- Algunos reemplazos quedan SIN sede ni frecuencia consistente + (ej. "VOLUMEN VARIABLE VRF" quedó sin "SEDE -" ni "- FREC"). +- Ideal: formato uniforme tipo "SEDE - CORTO - FREC" para todos. + +Ideas (opcional, cuando haya tiempo): +1. Mover `_PERS_REEMPLAZOS` a un JSON en GitHub o tabla Supabase → editable sin tocar código. +2. Normalizar TODOS los nombres con un mismo criterio (sede + nombre corto + frecuencia), + no solo los VRF/CO2 ya hechos. + +ESTADO: opcional. No urgente; los reemplazos actuales funcionan. + +## 8. Manual de tablas de Supabase — PENDIENTE +Mantener UN solo documento que liste cada tabla de Supabase y para qué sirve +(evitar olvidarse). Ej.: +- `datos_unificados` → leads (proyecto ogzjtkxnfs...). +- `basebi_programacion` → pauta/estado/contar por num_indice (proyecto uztqscimts...). +- `cartera_junta` → cartera total combinada por teléfono + es_origen (proyecto uztqscimts...). +- (futuras: `programa_alias` → diccionario de normalización). +Cada vez que se cree una tabla nueva, agregarla aquí. + +## 9. Normalización de programa/sede en cartera_junta + alerta de valores nuevos — PENDIENTE +Problema: en `cartera_junta` hay valores repetidos con distinta escritura +(CÁMARAS/CAMARAS, GESTION/GESTIÓN, VRF / VRV vs VRF, SUP. DE OBRAS/SUPER.OBRAS/ +SUPERVISIÓN DE OBRAS, DIPLONADO REF typo, LINA→LIMA) y basura (vacío, "0"). +Como la tabla se regenera a diario (GitHub Actions), limpiar a mano NO sirve +(se sobrescribe). Hay que normalizar en la GENERACIÓN. + +Plan acordado (mínimo de tablas): +- En `carga_cartera.py`: (a) reglas automáticas → MAYÚSCULAS + sin acentos + sin + espacios extra (une CÁMARAS=CAMARAS, GESTIÓN=GESTION, etc. sin mantener nada); + (b) UNA tabla-diccionario `programa_alias` (y sede) en Supabase con + alias → valor_correcto para los sinónimos/typos que las reglas no resuelven. +- Detección de NUEVOS: NO usar tabla de pendientes. Calcular al vuelo en el + dashboard, comparando los valores únicos de cartera_junta vs el diccionario. +- Alerta: 🔔 campanita/panel de notificaciones en el dashboard. Silencioso si todo + es conocido; si aparece sede/programa nuevo no reconocido, se enciende con la + lista para que el usuario decida (unir como alias o dejar como nuevo real). +- Decidir aparte: valores vacíos y "0" → dejar o convertir a SIN PROGRAMA/SIN SEDE. + +## 10. Clasificar valores no identificados DESDE la campanita — MEJORA OPCIONAL +Hoy la campanita 🔔 solo AVISA de los programas/sedes no identificados; para +corregirlos hay que ir a Supabase y agregar la fila en `alias_normalizacion`. +Mejora: permitir hacerlo desde la misma web, en la notificación: +- Al lado de cada valor no identificado, un desplegable con los programas/sedes + YA EXISTENTES (canónicos) + botón "Asignar". +- Al asignar, el backend inserta la fila (alias → correcto) en `alias_normalizacion` + directamente desde la web (nuevo endpoint POST), sin entrar a Supabase. +- Así se limpia al instante y en la siguiente corrida del workflow ya sale unido. +ESTADO: opcional. Hoy funciona avisando; esto solo agiliza la corrección. + +--- + +(Agregar aquí cualquier otro pendiente que vaya saliendo.) diff --git a/INICIAR_LEADS.bat b/INICIAR_LEADS.bat new file mode 100644 index 0000000..afec3d1 --- /dev/null +++ b/INICIAR_LEADS.bat @@ -0,0 +1,12 @@ +@echo off +REM ============================================================ +REM INICIAR DASHBOARD LEADS - Backend (8001) + Frontend (5174) +REM ============================================================ +echo Iniciando BACKEND y FRONTEND de LEADS... + +start "LEADS - BACKEND" cmd /k ""%~dp0_run_backend.bat"" +start "LEADS - FRONTEND" cmd /k ""%~dp0_run_frontend.bat"" + +echo Listo. Se abrieron 2 ventanas (Backend y Frontend). +echo Cuando cargue, abre el navegador en: http://localhost:5174 +timeout /t 4 >nul diff --git a/OPTIMIZACION_CACHE.md b/OPTIMIZACION_CACHE.md new file mode 100644 index 0000000..a9603b5 --- /dev/null +++ b/OPTIMIZACION_CACHE.md @@ -0,0 +1,78 @@ +# OPTIMIZACIÓN — Caché de descargas pesadas (reutilizable en Ventas, Comisiones, etc.) + +## El problema (síntoma) +Al cambiar un filtro (mes, sede, etc.) el dashboard se demora 15-130 segundos, +aunque los datos "ya deberían estar cargados". Se siente como si cada filtro +volviera a descargar todo desde cero. + +## La causa raíz +Una función que **descarga datos de una fuente externa** (Supabase, SQL Server, +Postgre, API) se llama SIN caché. Cada vez que el usuario filtra, esa función +**vuelve a descargar TODO** desde internet. + +En LEADS el culpable fue `traer_cartera_rows()` (bajaba 86,000 filas de Supabase +de 1000 en 1000 = 86 pedidos en serie ≈ 52-130s) y se re-descargaba en CADA +cambio de mes, porque `matriz_web_formulario` la llamaba directo: + +```python +# ANTES (malo): re-descarga en cada llamada +def _load(): + rows = get_dm().traer_cartera_rows() # baja 86k filas CADA vez + ... +``` + +## Cómo diagnosticarlo (script de tiempos) +Medir cada parte por separado para encontrar QUÉ tarda. Clave: medir un cambio +de mes CON los insumos ya cargados (2a vez). Si sigue lento, algo se re-descarga. + +```python +import time, services as S +def cron(label, fn): + t0 = time.time(); fn(); print(f" {label:38} {time.time()-t0:6.1f}s") + +cron("dashboard MES1 (1a vez)", lambda: S.mi_funcion("2026","1",...)) +cron("dashboard MES2 (2a vez)", lambda: S.mi_funcion("2026","2",...)) # <-- debe ser rapido +# Si MES2 sale lento, desglosar: medir cada traer_* y cada matriz por separado +# hasta ver cual linea se lleva los segundos (casi siempre un traer_* sin cache). +``` + +## La solución (2 líneas, NO toca lógica ni resultados) +Envolver la descarga pesada en `cache_get_or_set` con clave GLOBAL, y que TODAS +las funciones usen ese helper cacheado en vez de descargar directo. + +```python +# 1) Helper cacheado (se baja UNA vez, se reutiliza) +def _cartera_rows_cache(): + return cache_get_or_set("cartera_rows", ("GLOBAL",), + lambda: get_dm().traer_cartera_rows()) + +# 2) Reemplazar TODAS las llamadas directas: +# get_dm().traer_cartera_rows() -> _cartera_rows_cache() +``` + +Resultado en LEADS: +- Cambiar de mes: de 69-133s -> 0.4s (solo filtra lo que ya está en memoria) +- La descarga pesada solo se paga 1 vez (en la precarga de arranque, en background). + +## Regla general para CUALQUIER dashboard (Ventas, Comisiones, Rentabilidad...) +1. Toda función `traer_*` / consulta a SQL/Supabase/API que se use en varias + vistas o en cada filtro, debe estar cacheada con `cache_get_or_set`. +2. NUNCA llamar `get_dm().traer_xxx()` directo dentro de un `_load` que depende + de filtros; usar el helper cacheado. +3. El filtro (mes/sede/programa) debe operar sobre datos YA en memoria, no + re-descargar. Filtrar en memoria = milisegundos. +4. Precargar en segundo plano (hilo daemon al startup) el mes actual/anterior + para que el usuario no espere la 1a carga. +5. El refresco periódico (cada 15 min) invalida caché -> considerar bajar datos + que cambian 1 vez al día (ej. cartera) con menos frecuencia, o recargar en + background sin dejar hueco. + +## Mejora opcional pendiente (no aplicada aún) +La 1a descarga de la cartera es lenta (100-155s) porque pagina de 1000 en 1000. +Subir el tamaño de lote (ej. limit=10000) reduce los viajes: de ~155s a ~10-15s. +Aplica a cualquier tabla grande que se baje paginada. + +## Verificación (que no cambien números) +Antes de optimizar, guardar una "línea base" con los totales actuales +(diag_base.json). Tras optimizar, comparar: deben ser IDÉNTICOS. La optimización +solo cambia CUÁNDO/CÓMO se descarga, nunca el cálculo. diff --git a/_run_backend.bat b/_run_backend.bat new file mode 100644 index 0000000..ecf8d40 --- /dev/null +++ b/_run_backend.bat @@ -0,0 +1,11 @@ +@echo off +REM Lanzador interno del BACKEND de LEADS +cd /d "%~dp0backend" +echo ==================================== +echo LEADS - BACKEND (FastAPI) puerto 8001 +echo Carpeta: %CD% +echo ==================================== +py -3.12 main.py +echo. +echo (El backend termino o fallo. Revisa los mensajes de arriba.) +pause diff --git a/_run_frontend.bat b/_run_frontend.bat new file mode 100644 index 0000000..3f7d2da --- /dev/null +++ b/_run_frontend.bat @@ -0,0 +1,12 @@ +@echo off +cd /d "%~dp0frontend" +echo ==================================== +echo LEADS - FRONTEND (Vite) puerto 5174 +echo Carpeta: %CD% +echo ==================================== +if not exist "node_modules\.bin\vite.cmd" call npm install +echo Iniciando servidor de desarrollo... +call npm run dev +echo. +echo (npm run dev termino o fallo.) +pause