Importar tareas
Migra tus tareas desde Notion (o cualquier otra herramienta) con sus comentarios, adjuntos, personas y fechas originales, sin duplicar al reimportar.
Trae tus tareas tal como estaban. La importación crea o actualiza las tareas de un proyecto a partir de un archivo JSON: con su descripción en Markdown, estado, prioridad, entorno y versión, quién la tiene asignada y quién la pidió, sus enlaces, sus comentarios (con su autor y su fecha) y sus archivos. Las fechas de creación, última edición y cierre se conservan.
Es idempotente: cada tarea se identifica por su source (p. ej. notion) y su externalId. Importar el mismo archivo otra vez actualiza esas tareas; no las duplica. Así puedes migrar, revisar y volver a importar con el mapeo corregido.
En dos pasos
# 1. Ensayo: no escribe nada. Dice qué se crearía o actualizaría y qué no mapeó
fxc tasks import notion.json --project-id <id> --dry-run
# 2. Importación de verdad (repetirla actualiza, no duplica)
fxc tasks import notion.json --project-id <id>
La CLI valida el archivo, sube las imágenes locales incrustadas en el Markdown, importa las tareas y después sube sus adjuntos. Los archivos que ya están en la tarea (mismo nombre y tamaño) se saltan al reimportar.
Desde un asistente, la herramienta MCP import_tasks hace lo mismo ({ file, projectId, dryRun }). Por HTTP es POST /projects/:id/tasks/import (añade ?dryRun=true para el ensayo); hace falta ser del equipo del proyecto. El API no recibe archivos: las rutas locales solo las entiende la CLI.
El archivo
{
"version": 1,
"source": "notion",
"statusMap": { "Not started": "No Iniciada", "In progress": "Working", "Completed": "Terminada" },
"priorityMap": { "High": "Alta", "Medium": "Media", "Low": "Baja" },
"createMissingPeople": true,
"tasks": [
{
"externalId": "1a2b3c",
"externalUrl": "https://www.notion.so/acme/Login-roto-1a2b3c",
"title": "El login falla con Safari",
"description": "Pasos:\n\n| Paso | Resultado |\n|---|---|\n| Abrir /login | OK |\n| Enviar | Error 500 |\n\n",
"status": "In progress",
"priority": "High",
"environment": "producción",
"version": "1.4.0",
"tags": ["auth", "safari"],
"assignee": "Leonardo",
"requester": "Alexander Navarro",
"createdAt": "2025-02-10T09:30:00Z",
"updatedAt": "2025-02-12T16:00:00Z",
"links": [{ "url": "https://github.com/acme/web/issues/42", "label": "Issue" }],
"comments": [
{
"author": "Leonardo",
"createdAt": "2025-02-11T10:00:00Z",
"content": "Lo reproduzco. Adjunto el informe.",
"attachments": ["./notion-import/informe.docx"]
}
],
"attachments": ["./notion-import/datos.xlsx"]
}
]
}
Tienes un ejemplo completo de cinco tareas en el repositorio: docs/examples/notion-import.json.
Nivel del archivo
| Campo | Tipo | Obligatorio | Qué hace |
|---|---|---|---|
version |
1 |
no | Versión del formato. |
source |
texto (≤ 50) | no (import) |
Origen por defecto de las tareas: notion, jira, trello… Forma parte de la clave de idempotencia. |
statusMap |
objeto | no | Nombre del estado en el origen → nombre del estado en FXCode. |
priorityMap |
objeto | no | Igual para las prioridades. |
createMissingPeople |
booleano | no (false) |
Crea como persona sin cuenta a quien no se encuentre. Sin él, solo se informa. |
createMissingReleases |
booleano | no (false) |
Crea las entregas nombradas en release que no existan en el proyecto. Sin él, solo se informa. |
tasks |
lista | sí | De 1 a 500 tareas. |
Cada tarea
| Campo | Tipo | Obligatorio | Notas |
|---|---|---|---|
externalId |
texto (≤ 200) | sí | Id de la tarea en el origen. Único por source dentro del archivo. |
source |
texto | no | Sobrescribe el source del archivo para esta tarea. |
externalUrl |
URL http(s) | no | Enlace a la tarea original. |
title |
texto (1–500) | sí | |
description |
Markdown (≤ 50.000) | no | Tablas, citas, listas e imágenes. |
status |
texto | no | Se traduce con statusMap; si no está, se busca tal cual. |
priority |
texto | no | Se traduce con priorityMap. |
environment |
texto (≤ 100) | no | P. ej. producción, staging. |
version |
texto (≤ 100) | no | P. ej. 1.4.0. |
tags |
lista de textos | no | Hasta 30. Al reimportar reemplaza las etiquetas. |
kind |
TASK | IDEA |
no | Por defecto, tarea. |
assignee |
email o nombre | no | Ver personas. |
requester |
email o nombre | no | Quién la pidió. |
createdAt, updatedAt, completedAt |
fecha ISO 8601 | no | 2025-02-10 o 2025-02-10T09:30:00Z. |
dueDate |
fecha ISO 8601 | no | Fecha de entrega; se queda el día (00:00 UTC). |
release |
texto (≤ 100) | no | Nombre de la entrega del proyecto (sin mayúsculas ni acentos). Sin release, una tarea con version se enlaza a la entrega que se llame igual, si existe. |
links |
lista { url, label? } |
no | Hasta 50. Se añaden los que faltan (por URL) y se actualiza su etiqueta. |
comments |
lista | no | Hasta 200. Ver comentarios. |
attachments |
lista de rutas | no | Hasta 20 archivos locales (≤ 10 MB cada uno), relativos al JSON. |
Lo que el archivo no trae no se toca al actualizar: una tarea reimportada sin description conserva la que tenía. Los campos que no son del formato se ignoran y aparecen en el informe.
Comentarios
| Campo | Tipo | Obligatorio | Notas |
|---|---|---|---|
content |
Markdown (≤ 20.000) | sí | Puede incrustar imágenes locales. |
author |
email o nombre | no | Se conserva aunque no tenga cuenta. |
createdAt |
fecha ISO 8601 | no | La fecha original. |
externalId |
texto | no | Clave del comentario. Sin ella se deriva de autor + fecha + contenido. |
attachments |
lista de rutas | no | Hasta 10 archivos, adjuntos a ese comentario. |
Los comentarios también son idempotentes: reimportar el mismo comentario lo actualiza. Si cambias su texto en el origen y no tiene externalId, entra como uno nuevo.
Estados y prioridades
statusMap traduce los nombres del origen a los tuyos, sin importar mayúsculas ni tildes. Un estado que no mapea deja la tarea sin estado (o con el que tenía, si ya existía) y sale en el informe. Si la tarea llega en un estado terminado y no trae completedAt, se fecha con su updatedAt.
Las prioridades funcionan igual. Si tu catálogo de prioridades está vacío, el API crea Alta, Media y Baja al arrancar; también puedes sembrarlas tú (es idempotente: con alguna prioridad ya creada no hace nada):
fxc settings priorities seed
fxc settings priorities list
Personas
assignee, requester y el author de cada comentario aceptan un email o un nombre exacto (sin importar mayúsculas ni tildes; vale el nombre o nombre y apellido):
- Por email se busca en todo el directorio de personas.
- Por nombre, entre el equipo del proyecto y las personas que ya cuelgan de sus tareas (así una persona creada en una importación anterior se reconoce al reimportar). Si dos personas comparten nombre, no se adivina: sale como
ambiguous. - Si no se encuentra y
createMissingPeopleestrue, se crea como persona sin cuenta. En la tarea se ve «Asignado a Leonardo» o «Pedido por Alexander Navarro» igual que con una cuenta. - Sin ese flag, el campo se deja vacío y la persona sale en el informe como
missing.
Si la persona tiene cuenta, la tarea queda asignada también a su usuario (aparece en «mis tareas»).
Imágenes y adjuntos
-
Imágenes incrustadas: la CLI busca
con rutas locales en las descripciones y los comentarios, las sube como imágenes públicas (png, jpg, webp, gif o avif, hasta 5 MB) y reescribe la URL. Las que ya sonhttps://se dejan como están. Las rutas con espacios valen escritas tal cual entre<…>o codificadas como las exporta Notion (image%203.png). -
Adjuntos: los de
attachmentsse suben a la tarea (o al comentario) después de importarla. Son privados: se descargan desde la tarea con un enlace temporal. Cualquier tipo de archivo (docx, xlsx, pdf, zip…), hasta 10 MB. -
Dry-run: no sube nada. El informe dice cuántas imágenes y adjuntos se subirían (
wouldUpload).
Reimportar sin deshacer tu limpieza
Reimportar el mismo archivo actualiza las tareas que ya existen con todo lo que trae el archivo: título, descripción, estado, prioridad, etiquetas, asignado… Si después de importar editaste tareas a mano, limita qué se actualiza con --fields (o fields en el archivo). Las tareas nuevas se crean completas igualmente.
# Añadir comentarios y fechas originales sin tocar títulos, estados ni etiquetas
fxc tasks import notion.json --project-id <id> --fields comments,dates --dry-run
fxc tasks import notion.json --project-id <id> --fields comments,dates
Campos: title, description, status, priority, environment, version, tags, kind, externalUrl, assignee, requester, dates (createdAt, updatedAt y completedAt), dueDate, release, links, comments y attachments. Con fields, la CLI solo sube las imágenes y los adjuntos que de verdad se van a escribir.
Limpiar después de importar
Una importación rara vez llega limpia: duplicadas, tareas viejas, etiquetas que ya no significan nada. Ordénalo desde la CLI (o seleccionando varias tareas en la web):
# Archivar lo que ya no aplica: sale de las listas, del avance del proyecto y del MCP (no se borra nada)
fxc tasks archive <id> <id> …
fxc tasks list --project-id <id> --archived # revisar lo archivado (unarchive lo recupera)
# Los mismos cambios a muchas tareas a la vez
fxc tasks update --ids a,b,c --status "No Iniciada" --priority Media --add-tag migrada --remove-tag notion-viejo
fxc tasks update --ids a,b --assigned-person-id <personId> # una persona sin cuenta
# Fusionar duplicadas en una: comentarios, adjuntos, enlaces, notas y solicitudes pasan a la destino
fxc tasks merge <destinoId> <duplicadaId> <duplicadaId>
# Ordenar el trabajo: relaciones, subtareas y la cola
fxc tasks link-task <id> --blocked-by <otraId>
fxc tasks update <id> --parent-id <madreId>
fxc tasks queue <id> --first
Reimportar respeta tu limpieza. Una tarea archivada sigue archivada al reimportar, y una fusionada también: la de origen conserva su source + externalId, así que la importación la actualiza (archivada) en vez de volver a crearla. Combínalo con --fields (por ejemplo --fields comments,dates) para que reimportar no pise los estados, las etiquetas o los títulos que corregiste a mano. Más en Kanban, cola y relaciones.
El informe
El ensayo y la importación devuelven lo mismo (con --json, tal cual):
{
"dryRun": true,
"summary": { "total": 5, "create": 4, "update": 1 },
"tasks": [{ "externalId": "1a2b3c", "source": "notion", "action": "update", "title": "…", "taskId": "cm…" }],
"unmapped": {
"statuses": [{ "name": "Blocked", "count": 2 }],
"priorities": [],
"people": [{ "ref": "Leonardo", "count": 3, "resolution": "will-create" }],
"fields": [{ "field": "tasks[].Sprint", "count": 5 }],
"releases": [{ "name": "Fase 9", "count": 4, "resolution": "missing" }]
},
"warnings": []
}
resolution es missing, ambiguous, will-create (ensayo con createMissingPeople) o created. En releases es missing (la tarea queda sin entrega), will-create (ensayo con createMissingReleases) o created. La CLI añade cuántas imágenes y adjuntos subiría o subió y cuáles no existen en disco.
La importación no envía correos ni una notificación por tarea: al terminar, las listas abiertas se refrescan una sola vez.
Límites
| Límite | Valor |
|---|---|
| Tareas por archivo | 500 |
| Título | 500 caracteres |
| Descripción | 50.000 caracteres (igual que al crear una tarea a mano) |
| Etiquetas por tarea | 30 |
| Enlaces por tarea | 50 |
| Comentarios por tarea | 200 (20.000 caracteres cada uno) |
| Adjuntos | 20 por tarea, 10 por comentario, 10 MB cada uno |
Entradas de statusMap / priorityMap |
100 |
¿Más de 500 tareas? Divide el archivo: como la clave es source + externalId, puedes importarlo por partes en cualquier orden.
Desde Notion
Exporta la base de datos como Markdown & CSV y convierte cada fila en una tarea del formato de arriba (un script corto o tu asistente lo hace): el id de la página va en externalId, su URL en externalUrl, la columna de estado en status y la de fecha de creación en createdAt. Las imágenes de la exportación quedan en carpetas junto al Markdown: referéncialas con rutas relativas al JSON y la CLI las sube. Una columna de fecha de entrega va en dueDate y una de sprint o hito en release (crea antes las entregas o pasa createMissingReleases: true).