excel-planner/docs/POWER_AUTOMATE_FLUJO.md

9.8 KiB

Flujo Power Automate — Puente de datos para Planner

Esta guia conecta Microsoft Planner con tu proyecto Python sin necesitar Azure Tenant ID ni Client ID.

Expresiones listas (fechas + responsables): ver docs/POWER_AUTOMATE_EXPRESIONES.md
Diagnostico: python scripts/diagnostico_pa.py

Python (script.py)  --HTTP POST-->  Power Automate  -->  Planner / Graph  -->  JSON  -->  Python

Paso 1: Crear el flujo

  1. Entra a make.powerautomate.com
  2. Clic en CrearFlujo instantaneo en la nube
  3. Nombre: Puente Planner a Python

Paso 2: Trigger HTTP

  1. Busca y selecciona: Cuando se recibe una solicitud HTTP

  2. Metodo: POST

  3. En Esquema JSON del cuerpo de solicitud, pega el contenido de: power_automate/trigger-schema.json

  4. Clic en Guardar. Power Automate generara la URL del trigger.

  5. Copia esa URL y pegala en tu .env:

DATA_SOURCE=powerautomate
POWER_AUTOMATE_URL=https://prod-00.westus.logic.azure.com/workflows/.../sig=...

La URL contiene sig=... que actua como llave secreta. No la compartas publicamente.


Paso 3: Obtener datos de Planner

Elige una de estas opciones segun lo que tengas disponible:

Opcion A — Conector Planner (mas simple)

Repite este bloque por cada plan que quieras monitorear:

# Accion Configuracion
1 Planner → Listar tareas Plan: selecciona tu plan
2 Seleccionar Transforma campos (ver expresiones abajo)
3 Anexar a variable de matriz Variable: todasLasTareas

Crear variable al inicio:

  • Accion: Inicializar variable
  • Nombre: todasLasTareas
  • Tipo: Matriz
  • Valor: []

Expresiones en "Seleccionar" (mapeo de campos):

Campo salida Expresion
grupo @{items('Apply_to_each_plan')?['title']} o nombre fijo del equipo
proyecto @{items('Apply_to_each_plan')?['title']}
plan_id @{items('Apply_to_each_plan')?['id']}
tarea @{item()?['title']}
tarea_id @{item()?['id']}
bucket @{item()?['bucketName']} si disponible, o vacio
estado Ver tabla de estados abajo
porcentaje @{item()?['percentComplete']}
prioridad @{item()?['priority']}
fecha_vencimiento @{item()?['dueDateTime']}
asignados @{item()?['assignedTo']} o resuelve con nombre
vencida @{and(item()?['dueDateTime'] < utcNow(), item()?['percentComplete'] < 100)}

Tabla de estados (expresion para campo estado):

@if(equals(item()?['percentComplete'], 100), 'Completada',
  if(greater(item()?['percentComplete'], 0), 'En progreso', 'Por hacer'))

Opcion B — HTTP a Microsoft Graph (mas completo)

Si tienes conector HTTP con Microsoft Entra ID (premium):

# Accion Configuracion
1 HTTP con Entra ID Metodo: GET
URL https://graph.microsoft.com/v1.0/me/planner/plans
2 Parse JSON Contenido: body de paso 1
3 Apply to each Planes del paso anterior
4 HTTP con Entra ID GET https://graph.microsoft.com/v1.0/planner/plans/@{items('Apply_to_each')?['id']}/tasks
5 Seleccionar Normalizar tareas
6 Anexar a variable Acumular en todasLasTareas

Paso 4: Agrupar tareas en proyectos

Despues de recolectar todas las tareas:

  1. Compose — Agrupa por plan_id usando expresiones, o
  2. Usa Union + Filter array por cada plan conocido, o
  3. Devuelve tareas planas y deja que Python agrupe (soportado)

Forma simple: devuelve el arreglo tareas y Python lo agrupa automaticamente.


Paso 5: Responder a Python

Agrega la accion final: Respuesta a una solicitud de Power Automate

Campo Valor
Codigo de estado 200
Encabezados Content-Type: application/json
Cuerpo JSON con estructura de abajo

Cuerpo de respuesta (copiar y adaptar)

Usa como referencia power_automate/respuesta-ejemplo.json.

Minimo requerido — solo tareas planas:

{
  "ok": true,
  "mensaje": "Datos extraidos correctamente",
  "tareas": [

Recomendado si usas Parte 2 (responsables) — incluye fechas y nombres:

{
  "ok": true,
  "mensaje": "Datos extraidos correctamente",
  "todasLasTareas": @{variables('todasLasTareas')},
  "tareas": @{variables('tareasFinales')}
}

Python fusiona automaticamente: fechas desde todasLasTareas y responsables desde tareas.

Copia la plantilla completa en power_automate/response-con-fechas.json.

Ejemplo de una tarea en tareas planas:

  "tareas": [
    {
      "grupo": "Mi Equipo",
      "proyecto": "Plan Q2",
      "plan_id": "abc123",
      "tarea": "Revisar documento",
      "tarea_id": "task001",
      "bucket": "En progreso",
      "estado": "En progreso",
      "porcentaje": 50,
      "prioridad": "Media",
      "fecha_inicio": "",
      "fecha_vencimiento": "2026-06-15T17:00:00Z",
      "asignados": "Juan Perez",
      "vencida": false,
      "creada": "",
      "actualizada": ""
    }
  ]
}

Respuesta completa con resumen por proyecto:

{
  "ok": true,
  "mensaje": "Datos extraidos correctamente",
  "proyectos": [ ... ]
}

Ver ejemplo completo en power_automate/respuesta-ejemplo.json.

Para prueba rapida (accion = test)

Si el body recibido tiene "accion": "test", responde:

{
  "ok": true,
  "mensaje": "Puente activo",
  "proyectos": []
}

Agrega una condicion al inicio:

Condicion: triggerBody()?['accion'] es igual a test
  Si si  → Respuesta con mensaje de prueba
  Si no  → Continuar extraccion de Planner

Paso 6: Activar el flujo

  1. Clic en Guardar
  2. Clic en Activar (o Encender)
  3. El flujo debe quedar en estado Activado

Paso 7: Probar desde Python

cd "ruta\Excel - Planner"
python script.py --test
python script.py

Si funciona veras:

[OK] Puente Power Automate conectado.
     Datos extraidos correctamente
     -> 3 proyecto(s), 45 tarea(s)

Diagrama del flujo completo

[HTTP POST recibido]
        |
        v
[accion == "test"?] ---si---> [Respuesta: ok + mensaje]
        |
       no
        v
[Inicializar variable: todasLasTareas = []]
        |
        v
[Por cada Plan de Planner]
        |
        +--> [Listar tareas del plan]
        +--> [Seleccionar / normalizar campos]
        +--> [Anexar a todasLasTareas]
        |
        v
[Respuesta HTTP 200]
  { "ok": true, "tareas": [...] }
        |
        v
[Python recibe JSON → Excel + Dashboard]

Fechas de vencimiento (fechaFin en el tablero)

Sintoma: el tablero muestra la columna Fecha Limite vacia.

Causa: la Parte 2 del flujo (tareasFinales) reconstruye el JSON sin copiar fecha_vencimiento.

Solucion A — Cambiar la respuesta HTTP (mas rapida, 1 minuto)

En la accion Respuesta, usa este cuerpo:

{
  "ok": true,
  "mensaje": "Datos extraidos correctamente",
  "todasLasTareas": @{variables('todasLasTareas')},
  "tareas": @{variables('tareasFinales')}
}

Asegurate de que la Parte 1 (Anexar a todasLasTareas) incluya:

Campo Expresion
fecha_vencimiento @{items('Aplicar_a_cada_uno_1')?['dueDateTime']}

Solucion B — Agregar fecha en Parte 2 (Anexar a tareasFinales)

En ambas ramas (Si y No) de la condicion, agrega al JSON de Anexar:

"fecha_vencimiento":"', coalesce(items('Aplicar_a_cada_uno_2')?['fecha_vencimiento'], items('Aplicar_a_cada_uno_2')?['dueDateTime'], ''), '"

Colocalo junto a los demas campos (grupo, proyecto, tarea, etc.).

Verificar

python -c "from services.powerautomate_bridge import PowerAutomateBridge; t=PowerAutomateBridge().fetch_raw({'accion':'listar_proyectos'})['tareas'][0]; print(t.get('fecha_vencimiento'))"

Debe imprimir una fecha ISO (ej. 2026-06-15T17:00:00Z), no None.


Solucion de problemas

Error Causa Solucion
401 / 403 URL incorrecta o flujo apagado Verifica URL y que el flujo este activado
404 URL del trigger mal copiada Copia de nuevo desde Power Automate
Timeout Muchas tareas Aumenta POWER_AUTOMATE_TIMEOUT=300 en .env
JSON invalido Respuesta mal formada Valida con respuesta-ejemplo.json
0 proyectos Plan sin tareas o permisos Verifica acceso al plan en Planner
Mismas tareas en todos los planes El bucle de PA lista todas las tareas sin filtrar por plan Ver seccion abajo

Error: mismas tareas en todos los proyectos

Sintoma: al cambiar de proyecto en el tablero, las tareas no cambian (o el TSV repite las mismas filas con distinto id).

Causa: en Power Automate, dentro de Aplicar a cada uno (planes), la accion Listar tareas devuelve las mismas tareas para cada plan y solo cambia el plan_id en el mapeo.

Solucion en el flujo 1 (extraccion):

Inicializar variable todasLasTareas = []

Aplicar a cada uno (Enumerar planes del grupo)
    Listar tareas
        Plan:  @items('Aplicar_a_cada_uno')?['id']    <-- plan ACTUAL del bucle
    Seleccionar (mapear campos)
        plan_id: @items('Aplicar_a_cada_uno')?['id']  <-- NO un plan fijo
        proyecto: @items('Aplicar_a_cada_uno')?['title']
    Anexar a variable todasLasTareas

Respuesta JSON con todasLasTareas

Importante:

  • En Listar tareas, el campo Plan debe ser dinamico (@items('Aplicar_a_cada_uno')?['id']), no un plan fijo.
  • En Seleccionar, plan_id y proyecto deben salir del plan del bucle, no de un valor hardcodeado.

Python deduplica por tarea_id cuando PA envia duplicados, pero cada plan debe traer solo sus tareas desde Planner.


Seguridad

  • No publiques la URL del trigger (contiene sig=)
  • No subas .env a git (ya esta en .gitignore)
  • Considera agregar un campo api_key en el body si compartes el flujo en tu organizacion