Hay una tentación enorme cuando tenés una idea: abrir el editor y empezar a escribir código. La adrenalina de ver algo funcionar rápido es difícil de resistir.
Con Ciphie no lo hice.
No porque tenga una disciplina especial. Sino porque me di cuenta de que no sabía exactamente qué quería construir. Tenía una dirección — un gestor de secretos local, cifrado, sin dependencias de la nube — pero no tenía claridad sobre cómo las piezas encajaban entre sí. Y en un proyecto de seguridad, empezar a codear sin esa claridad es una forma segura de construir algo con agujeros que no vas a ver hasta que sea tarde.
Así que antes de escribir una sola línea, me senté con papel y dibujé el sistema.
La pregunta que ordenó todo
El primer ejercicio fue simple: escribir en una sola oración qué hacía Ciphie.
“Un gestor de secretos que cifra cada entrada individualmente, corre sin servidor central y requiere autenticación para acceder.”
Esa frase parece obvia. Pero escribirla me obligó a tomar tres decisiones que no había tomado todavía: cifrado por entrada (no del archivo completo), sin servidor central (toda la lógica y los datos viven en la máquina del usuario), y autenticación (usuarios, contraseñas, sesiones). Ciphie sí puede hacer llamadas de red para features opcionales — verificación de cuenta por email, SMS via Twilio, TOTP — pero ninguna de esas integraciones es requerida para que el núcleo funcione.
Cada una de esas palabras abría preguntas nuevas. ¿Cómo se deriva la clave de cifrado? ¿Dónde vive la base de datos? ¿Qué pasa si alguien falla el login cinco veces seguidas?
Antes de tocar el teclado, tenía una página llena de preguntas.
Dibujar el flujo, no el código
Lo primero que dibujé no fue una arquitectura técnica. Fue el recorrido de un usuario hipotético a través de la aplicación.
Alguien abre Ciphie. Ingresa su contraseña. Pasa el segundo factor. Ve su lista de secretos. Agrega uno nuevo. Lo recupera cuando lo necesita. Cierra la app.
Ese flujo parece trivial. Pero cuando lo dibujás paso a paso, aparecen las preguntas que importan: ¿qué pasa si la contraseña es incorrecta? ¿Cuántas veces puede fallar antes de bloquearse? ¿Cómo se maneja una sesión? ¿Qué sucede si la app queda abierta sin actividad?
Cada bifurcación en el flujo era una decisión de diseño. Y es mucho más fácil tomar esas decisiones en papel, donde borrar y rehacer no cuesta nada, que en código, donde cada cambio arrastra consecuencias.
Las tres capas que aparecieron solas
Cuando terminé de dibujar el flujo, algo interesante pasó: el sistema se organizó naturalmente en tres capas sin que yo lo planeara explícitamente.
La capa de autenticación. Todo lo que tiene que ver con usuarios: registro, login, hashing de contraseñas, segundo factor, bloqueo por intentos fallidos, cierre de sesión automático. Esta capa no sabe nada de secretos — solo sabe quién está adentro y quién no.
La capa de datos. Todo lo que tiene que ver con secretos: guardarlos, cifrarlos, recuperarlos, organizarlos por categoría, asociarlos a un usuario. Esta capa no sabe nada de autenticación — recibe un usuario ya verificado y opera sobre sus datos.
La capa de interfaz. Todo lo visual: la ventana, los campos, los botones, los mensajes de error. Esta capa no sabe nada de cifrado ni de autenticación — recibe información para mostrar y devuelve acciones del usuario.
Esa separación no fue una decisión arquitectónica deliberada. Emergió de dibujar el flujo y preguntarme: ¿quién sabe qué acá?
Lo que el papel reveló que el código hubiera ocultado
Hubo un momento específico en el diseño que me alegra haber resuelto antes de codear.
Estaba dibujando el flujo de cifrado y me pregunté: ¿la clave de cifrado viene de la contraseña del usuario o de una clave maestra separada?
Si viene de la contraseña, cambiar la contraseña implica recifrar todos los secretos. Si viene de una clave maestra, esa clave necesita estar guardada en algún lugar — y ese lugar se convierte en el punto más sensible del sistema.
Ninguna de las dos opciones es perfecta. Pero es un trade-off que necesitaba entender antes de empezar a construir, porque afecta toda la arquitectura. Si hubiera empezado a codear sin pensar en esto, hubiera tomado una decisión implícita sin darme cuenta — probablemente la más conveniente en ese momento, no la más correcta.
En papel, pude ver el trade-off completo antes de comprometerme con una dirección.
Cuándo el papel se termina
Hay un punto en el que seguir diseñando en papel se convierte en una forma de no empezar. Ese punto llegó cuando las preguntas que quedaban solo podían responderse escribiendo código.
¿Cómo se va a sentir el flujo de login? ¿El segundo factor va a ser lo suficientemente fluido? ¿La interfaz va a comunicar bien el estado del sistema?
Esas preguntas no tienen respuesta en papel. Solo se responden cuando hay algo corriendo.
El diseño previo no reemplaza la experimentación — la hace más eficiente. Llegás al editor sabiendo qué estás construyendo, con las decisiones grandes ya tomadas, y podés concentrarte en las preguntas que solo el código puede responder.
Lo que me llevaría a cualquier proyecto nuevo
Si tuviera que resumir lo que aprendí de este proceso en algo concreto, sería esto:
Antes de abrir el editor, escribí en una sola oración qué hace el proyecto. Después dibujá el recorrido completo de un usuario. Después preguntate, en cada paso: ¿quién sabe qué acá?
Esas tres cosas no garantizan un buen proyecto. Pero garantizan que cuando escribas la primera línea de código, sabés por qué la estás escribiendo.
Ciphie todavía no está publicado — estoy terminando los últimos detalles antes de la primera versión.
There’s an enormous temptation when you have an idea: open the editor and start writing code. The adrenaline of seeing something work quickly is hard to resist.
With Ciphie I didn’t do that.
Not because I have special discipline. But because I realized I didn’t know exactly what I wanted to build. I had a direction — a local secrets manager, encrypted, with no cloud dependencies — but I didn’t have clarity on how the pieces fit together. And in a security project, starting to code without that clarity is a sure way to build something with holes you won’t see until it’s too late.
So before writing a single line, I sat down with paper and drew the system.
The question that organized everything
The first exercise was simple: write in a single sentence what Ciphie did.
“A secrets manager that encrypts each entry individually, runs without a central server, and requires authentication to access.”
That sentence seems obvious. But writing it forced me to make three decisions I hadn’t made yet: per-entry encryption (not the whole file), no central server (all logic and data live on the user’s machine), and authentication (users, passwords, sessions). Ciphie can make network calls for optional features — email account verification, SMS via Twilio, TOTP — but none of those integrations are required for the core to work.
Each of those words opened new questions. How is the encryption key derived? Where does the database live? What happens if someone fails the login five times in a row?
Before touching the keyboard, I had a page full of questions.
Drawing the flow, not the code
The first thing I drew wasn’t a technical architecture. It was the journey of a hypothetical user through the application.
Someone opens Ciphie. Enters their password. Passes the second factor. Sees their list of secrets. Adds a new one. Retrieves it when needed. Closes the app.
That flow seems trivial. But when you draw it step by step, the questions that matter appear: what happens if the password is wrong? How many times can it fail before locking? How is a session managed? What happens if the app is left open without activity?
Every branch in the flow was a design decision. And it’s much easier to make those decisions on paper, where erasing and redoing costs nothing, than in code, where every change drags consequences.
The three layers that appeared on their own
When I finished drawing the flow, something interesting happened: the system naturally organized itself into three layers without me explicitly planning it.
The authentication layer. Everything related to users: registration, login, password hashing, second factor, lockout on failed attempts, automatic session logout. This layer knows nothing about secrets — it only knows who’s in and who’s not.
The data layer. Everything related to secrets: storing them, encrypting them, retrieving them, organizing them by category, associating them with a user. This layer knows nothing about authentication — it receives an already-verified user and operates on their data.
The interface layer. Everything visual: the window, the fields, the buttons, the error messages. This layer knows nothing about encryption or authentication — it receives information to display and returns user actions.
That separation wasn’t a deliberate architectural decision. It emerged from drawing the flow and asking myself: who knows what here?
What the paper revealed that the code would have hidden
There was a specific moment in the design that I’m glad I resolved before coding.
I was drawing the encryption flow and asked myself: does the encryption key come from the user’s password or from a separate master key?
If it comes from the password, changing the password means re-encrypting all secrets. If it comes from a master key, that key needs to be stored somewhere — and that place becomes the most sensitive point in the system.
Neither option is perfect. But it’s a trade-off I needed to understand before starting to build, because it affects the entire architecture. If I had started coding without thinking about this, I would have made an implicit decision without realizing it — probably the most convenient one at the time, not the most correct.
On paper, I could see the complete trade-off before committing to a direction.
When the paper runs out
There’s a point where continuing to design on paper becomes a way of not starting. That point came when the remaining questions could only be answered by writing code.
How will the login flow feel? Will the second factor be smooth enough? Will the interface communicate the system’s state well?
Those questions have no answer on paper. They’re only answered when something is running.
Prior design doesn’t replace experimentation — it makes it more efficient. You arrive at the editor knowing what you’re building, with the big decisions already made, and you can focus on the questions that only code can answer.
What I’d take to any new project
If I had to summarize what I learned from this process into something concrete, it would be this:
Before opening the editor, write in a single sentence what the project does. Then draw the complete journey of a user. Then ask yourself, at every step: who knows what here?
Those three things don’t guarantee a good project. But they guarantee that when you write the first line of code, you know why you’re writing it.
Ciphie isn’t published yet — I’m finishing the last details before the first version.