Hay un commit mío de septiembre de 2024 que se llama "never give up!!", con dos exclamaciones. Si alguna vez te ves escribiendo un mensaje de commit para darte ánimos, para un momento y pregúntate qué estás defendiendo. Yo no lo hice, y me costó dos meses.
Lo que estaba defendiendo era un editor de bloques. La idea no tenía nada de raro: cada párrafo es un bloque, lo arrastras, lo reordenas, y cada bloque tiene su botón de IA para reescribirlo. Notion lleva años haciéndolo. Lo monté con dnd-kit para el arrastre y una instancia de editor por bloque. El componente Block recibía id, index, content, onDelete, onContentChange, una función regenerateContent con cinco parámetros, la lista entera de bloques para dar contexto a la IA y un AbortController guardado en un ref para cortar la generación a medias.
Y funcionaba. Eso es lo peligroso. El arrastre, que era lo que me daba miedo, salió bien a la primera. Lo que vino después fue apareciendo de uno en uno, y cada problema parecía el último.
Cuatro problemas que no vi venir, en el orden en que llegaron
Primero, meter contenido desde fuera. Cada bloque tenía un useEffect que comparaba content !== editor.getHTML() y, si no coincidían, llamaba a setContent. Comparar HTML serializado como string para decidir si el estado ha cambiado es de esas cosas que funcionan hasta el día en que el editor normaliza un atributo, o reordena algo, y te encuentras un bucle o un cursor que salta al principio.
Luego la selección. Si cada bloque es su propio editor, no puedes seleccionar desde la mitad de un párrafo hasta la mitad del siguiente. Esto lo descubrí intentando copiar un trozo de texto de mi propio editor. No tiene arreglo parcial: o tienes un documento, o tienes N documentos pegados con celo.
Después, deshacer. Cada instancia tiene su propia pila de historial, así que Ctrl+Z deshace lo último que pasó en el bloque donde está el cursor, que no tiene por qué ser lo último que tocaste en el documento. Nadie espera ese comportamiento, y explicarlo en un tooltip sería reconocer la derrota.
Y por último, pegar. Pegas tres párrafos dentro de un bloque y se quedan los tres dentro de ese bloque. Para partirlos tienes que reimplementar a mano lo que el esquema de un documento ya sabe hacer.
Lo que me fastidia, mirándolo ahora, es que los cuatro son el mismo problema. Selección, historial, pegado y esquema forman un solo sistema, y yo lo había partido en N trozos. Ninguno de los cuatro se podía arreglar sin deshacer esa decisión, y esa decisión era todo el diseño.
block2/
A mediados de octubre, al lado de la carpeta block/ apareció una carpeta block2/ con tres ficheros: el editor, la barra de herramientas y un hook. El hook tenía unas cuarenta líneas. Un único editor de Tiptap con seis extensiones en un array (StarterKit, TextStyle, FontFamily, TextAlign, Highlight, Placeholder), y con eso ya venían el historial unificado, la selección continua y el pegado respetando el esquema. En noviembre llegaron las listas de tareas, los bloques de código y las imágenes por URL, cada una con un import y una entrada más en el array.
La reescritura fue más corta que el prototipo. Pero las dos carpetas convivieron semanas, y eso fue lo que realmente me salió caro. Cuando creas una carpeta que se llama block2 ya sabes que la otra no tiene arreglo, aunque todavía no te atrevas a decirlo en voz alta.
Compáralo con otro caso: la primera página de un mapa en 3D duró diez días antes de que una rama map-v2 la sustituyera entera. Mismo patrón, pero corté en diez días en vez de en dos meses. No creo que supiera más de mapas 3D que de editores; sospecho que simplemente me dolía menos tirarlo.
Las señales que miro ahora
No tengo una regla, tengo una lista de cosas que me hacen sospechar. Ninguna es definitiva por sí sola, pero dos juntas suelen bastar:
- Estoy sincronizando estado comparando representaciones serializadas (HTML, JSON en string) en vez de tener una sola fuente.
- Los bugs nuevos no están en ninguna pieza concreta, están en cómo se hablan dos piezas.
- He creado algo que termina en
2,-v2o-newy el original sigue vivo. - Estoy escribiendo código para imitar un comportamiento que el usuario da por hecho (Ctrl+Z, seleccionar, pegar) en lugar de código de mi dominio.
- Mis mensajes de commit han empezado a tener exclamaciones.
La segunda es la que más me ha servido. Si lo difícil es la primera pantalla, casi siempre lo puedes hacer tú. Si lo difícil aparece en la interacción entre piezas que al empezar no veías, lo más probable es que alguien ya haya pasado años resolviéndolo, y ese es el terreno donde una librería gana. Editores de texto enriquecido, sesiones y auth, fechas con zona horaria, sistemas de estilos. El CSS de este mismo sitio, por ejemplo: tenía un fichero de 1.681 líneas con clases propias, y al pasarlo a componentes solo sobrevivieron los tokens de color, 113 líneas en globals.css. Todo lo demás era un framework peor documentado que había escrito sin darme cuenta.
Lo contrario también vale. Si lo difícil es tu lógica de negocio, no hay librería que te salve, y meter una solo añade una capa que entender.
Lo que cambié
Ahora empiezo por la librería y salgo de ella solo si me bloquea. Ya sé que eso tiene su propio coste, y que el comentario típico es "y luego te pasas una semana peleándote con la API de extensiones de otro". Es verdad, a veces pasa. Pero pelearte con una API que tiene documentación y usuarios es más barato que pelearte con la tuya, que no tiene ni lo uno ni lo otro.
Un editor colaborativo que necesité el verano pasado salió de una plantilla de PlateJS con Yjs encima, y a los tres días del primer commit ya se veían en pantalla los cursores de otros usuarios. Unos meses antes habría empezado por un Block con cinco props. Para una herramienta de notas en Markdown ni me planteé el editor: CodeMirror, highlight.js importado en el CSS, y a lo mío.
El caso más rápido que tengo es de abril de 2025. Escribí un agente con herramientas, un agent.ts con sus tools de contexto, y al día siguiente hay un commit que lo elimina y usa directamente el streaming del proveedor. Menos de veinticuatro horas entre escribirlo y borrarlo. Me da un poco de pena ese fichero, pero es el mejor resultado de todos los que cuento aquí.
¿Me arrepiento de las tres semanas del editor a mano? Pues no del todo. Sé qué hace el pegado en un editor porque lo he implementado mal, y eso no lo aprendes leyendo la documentación de Tiptap. Esas tres semanas las pagaría otra vez. Las seis siguientes, con block/ y block2/ viviendo juntos, esas no.
