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
- Entra a make.powerautomate.com
- Clic en Crear → Flujo instantaneo en la nube
- Nombre:
Puente Planner a Python
Paso 2: Trigger HTTP
-
Busca y selecciona: Cuando se recibe una solicitud HTTP
-
Metodo: POST
-
En Esquema JSON del cuerpo de solicitud, pega el contenido de:
power_automate/trigger-schema.json -
Clic en Guardar. Power Automate generara la URL del trigger.
-
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:
- Compose — Agrupa por
plan_idusando expresiones, o - Usa Union + Filter array por cada plan conocido, o
- Devuelve
tareasplanas 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
- Clic en Guardar
- Clic en Activar (o Encender)
- 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_idyproyectodeben 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
.enva git (ya esta en.gitignore) - Considera agregar un campo
api_keyen el body si compartes el flujo en tu organizacion