Tutorial de EELisp

Aprende EELisp construyendo proyectos reales. Desde tu primera expresión hasta una calculadora de interés compuesto, un control de gastos y un organizador personal inspirado en Lotus Agenda.

Contenido

  1. Tu primera expresión Básico
  2. Variables y funciones Básico
  3. Listas y funciones de orden superior Básico
  4. Diccionarios Básico
  5. Calculadora de interés compuesto Proyecto
  6. Gestor de contactos con tablas Proyecto
  7. Control de gastos Proyecto
  8. Creación de una lista de tareas Proyecto
  9. Libro de recetas con navegación y edición Proyecto
  10. Formularios calculadora con campos computados Proyecto
  11. Agenda personal: elementos y categorías Proyecto
  12. Reglas de autocategorización Proyecto
  13. Vistas — Perspectivas dinámicas Proyecto
  14. Integración con calendario Proyecto
  15. Entrada inteligente y HTTP Proyecto
  16. Elementos recurrentes y plantillas Proyecto
  17. Múltiples agendas Proyecto
  18. Barra lateral de agenda e integración con calendario Proyecto
  19. Consejos y patrones Referencia

1. Tu primera expresión

Abre la barra lateral del REPL en EEditor con ⌘⇧L. Aparece como un panel en el lado derecho del editor y permanece abierta mientras cambias de archivo. Escribe una expresión y pulsa ⌘Return para evaluarla. El historial de comandos se guarda entre sesiones — usa las flechas Arriba/Abajo para recuperar comandos anteriores.

En EELisp, todo es una expresión entre paréntesis. El primer elemento es la función y el resto son argumentos:

;; Aritmética — el operador va primero
(+ 1 2)              ; → 3
(+ 1 2 3 4)          ; → 10  (¡variádica!)
(* 6 7)              ; → 42
(/ 100 3)            ; → 33.333...

;; Anidamiento — las expresiones internas se evalúan primero
(+ (* 3 4) (- 10 5))  ; → 17  (12 + 5)

;; Cadenas de texto
(str "Hello, " "world!")  ; → "Hello, world!"
(str-upper "hello")          ; → "HELLO"

;; Funciones matemáticas
(pow 2 10)            ; → 1024
(round 3.14159 2)    ; → 3.14  (redondear a N decimales)
(abs -42)             ; → 42

;; Las comparaciones devuelven true o false
(> 10 5)              ; → true
(= 1 1)              ; → true
Consejo: No necesitas espacios entre operadores y números. (+1 2) funciona igual que (+ 1 2).

2. Variables y funciones

Definir variables

Usa def para asignar un nombre a un valor:

(def name "Alice")
(def age 30)
(def pi 3.14159)

(str "Hi, " name "! You are " age " years old.")
; → "Hi, Alice! You are 30 years old."

Definir funciones

Usa defn para crear funciones con nombre:

;; Una función que duplica un número
(defn double (x)
  (* x 2))

(double 21)  ; → 42

;; Una función con múltiples parámetros
(defn greet (name greeting)
  (str greeting ", " name "!"))

(greet "Bob" "Good morning")
; → "Good morning, Bob!"

;; Lógica condicional con if
(defn abs-val (n)
  (if (< n 0) (- 0 n) n))

(abs-val -5)  ; → 5
(abs-val 3)   ; → 3

Funciones anónimas

Usa fn para funciones de un solo uso sin nombre:

(map (fn (x) (* x x)) '(1 2 3 4 5))
; → (1 4 9 16 25)

3. Listas y funciones de orden superior

Las listas son el pan de cada día de Lisp. Contienen secuencias ordenadas de valores:

;; Crear listas
(def numbers (list 1 2 3 4 5))
(def fruits '("apple" "banana" "cherry"))

;; Acceder a elementos
(first numbers)    ; → 1
(second numbers)   ; → 2
(last numbers)     ; → 5
(nth fruits 1)     ; → "banana"
(length numbers)   ; → 5

;; Construir listas
(cons 0 numbers)        ; → (0 1 2 3 4 5)
(append numbers '(6 7)) ; → (1 2 3 4 5 6 7)
(range 1 11)            ; → (1 2 3 4 5 6 7 8 9 10)

Map, Filter, Reduce

Las tres operaciones más potentes sobre listas:

;; map — aplica una función a cada elemento
(map double (range 1 6))
; → (2 4 6 8 10)

;; filter — conserva los elementos que cumplen una condición
(filter even? (range 1 11))
; → (2 4 6 8 10)

;; reduce — combina todos los elementos en un solo valor
(reduce + 0 (range 1 101))
; → 5050  (suma del 1 al 100)

;; Combínalos: suma de cuadrados de números pares del 1 al 10
(reduce + 0
  (map (fn (x) (* x x))
    (filter even? (range 1 11))))
; → 220  (4 + 16 + 36 + 64 + 100)

4. Diccionarios

Los diccionarios almacenan pares clave-valor usando palabras clave (con el prefijo :):

;; Crear un diccionario
(def person {:name "Alice" :age 30 :city "NYC"})

;; Acceder a valores
(dict-get person :name)   ; → "Alice"
(dict-get person :age)    ; → 30

;; Actualizar (devuelve un nuevo diccionario)
(def older (dict-set person :age 31))
(dict-get older :age)     ; → 31

;; Inspeccionar
(dict-keys person)        ; → (:name :age :city)
(dict-values person)      ; → ("Alice" 30 "NYC")
(dict-has person :email)  ; → false

;; Fusionar dos diccionarios (el segundo prevalece en conflictos)
(dict-merge person {:email "alice@example.com" :age 31})
; → {:name "Alice" :age 31 :city "NYC" :email "alice@example.com"}

5. Proyecto: Calculadora de interés compuesto

Vamos a construir una calculadora financiera real que computa el interés compuesto anual. Esta es la fórmula:

A = P × (1 + r/n)^(n×t)

Donde P = capital inicial, r = tasa anual, n = capitalizaciones por año, t = años.

Definir la función principal

;; Fórmula de interés compuesto usando pow
;; A = P * (1 + r/n)^(n*t)
(defn compound-interest (principal rate compounds-per-year years)
  (let ((r (/ rate 100))
        (n compounds-per-year))
    (* principal (pow (+ 1 (/ r n)) (* n years)))))

;; Prueba: 1000 $ al 5% anual, capitalización mensual, durante 10 años
(round (compound-interest 1000 5 12 10) 2)
; → 1647.01  (¡has ganado 647,01 $ en intereses!)

Añadir un desglose anual

;; Mostrar el saldo al final de cada año
(defn yearly-breakdown (principal rate compounds-per-year years)
  (println (str "Investment: $" principal " at " rate "%"))
  (println (str "Compounding: " compounds-per-year "x/year for " years " years"))
  (println "---")
  (for-each year (range 1 (+ years 1))
    (let ((balance (compound-interest principal rate compounds-per-year year))
          (interest (- balance principal)))
      (println (str "Year " year ": $"
                   (round balance 2) "  (+"
                   (round interest 2) " interest)")))))

(yearly-breakdown 10000 7 12 5)
> (yearly-breakdown 10000 7 12 5) Investment: $10000 at 7% Compounding: 12x/year for 5 years --- Year 1: $10723 (+723 interest) Year 2: $11497 (+1497 interest) Year 3: $12327 (+2327 interest) Year 4: $13217 (+3217 interest) Year 5: $14171 (+4171 interest)

Comparar diferentes tasas

;; Comparar múltiples tasas de interés lado a lado
(defn compare-rates (principal years . rates)
  (println (str "Comparing $" principal " over " years " years:"))
  (for-each rate rates
    (let ((final (compound-interest principal rate 12 years))
          (gain (round (- final principal) 2)))
      (println (str "  " rate "% → $" (round final 2) "  (+" gain ")")))))

(compare-rates 10000 20 3 5 7 10)
> (compare-rates 10000 20 3 5 7 10) Comparing $10000 over 20 years: 3% → $18167 (+8167) 5% → $27126 (+17126) 7% → $40387 (+30387) 10% → $72890 (+62890)
Lección aprendida: Las funciones en EELisp pueden recibir parámetros rest (. rates) para aceptar un número variable de argumentos. Esto es perfecto para comparar múltiples valores.

6. Proyecto: Gestor de contactos con tablas

EELisp tiene una base de datos SQLite integrada. Vamos a usarla para gestionar contactos — como un mini dBASE.

Crear la tabla

;; Definir una tabla de contactos con campos tipados
(deftable contacts
  (name:string email:string phone:string city:string age:number))

;; La tabla está creada. Puedes verificar:
(describe contacts)
(tables)   ; → ("contacts")

Insertar registros

;; Insertar contactos — cada uno devuelve el nuevo registro con un ID automático
(insert contacts {:name "Alice"   :email "alice@mail.com"   :phone "555-0101" :city "New York"    :age 30})
(insert contacts {:name "Bob"     :email "bob@mail.com"     :phone "555-0102" :city "San Francisco" :age 25})
(insert contacts {:name "Carol"   :email "carol@mail.com"   :phone "555-0103" :city "New York"    :age 35})
(insert contacts {:name "Dan"     :email "dan@mail.com"     :phone "555-0104" :city "Chicago"       :age 28})
(insert contacts {:name "Eve"     :email "eve@mail.com"     :phone "555-0105" :city "San Francisco" :age 32})

Consultar tus datos

;; Ver todos los contactos (se muestran como una tabla formateada)
(query contacts)

;; Buscar contactos en Nueva York
(query contacts :where "city = ?" :params (list "New York"))

;; Buscar contactos mayores de 30, ordenados por nombre
(query contacts :where "age > ?" :params (list 30) :order "name")

;; Contar contactos
(count-records contacts)  ; → 5

;; Seleccionar solo columnas específicas
(query contacts :select "name, city" :order "city")

Actualizar y eliminar

;; Actualizar la ciudad de Bob (registro ID 2)
(update contacts 2 {:city "Los Angeles"})

;; Verificar el cambio
(query contacts :where "name = ?" :params (list "Bob"))

;; Eliminación lógica de Dan (se marca como eliminado pero no desaparece)
(delete contacts 4)
(count-records contacts)  ; → 4  (Dan está oculto)

;; Purgar registros eliminados permanentemente
(pack contacts)
(count-records contacts)  ; → 4

Trabajar con resultados de consultas en código

;; Extraer registros de una consulta y procesarlos
(def nyc-people
  (records (query contacts :where "city = ?" :params (list "New York"))))

;; Obtener nombres de contactos en NYC
(map (fn (r) (field-get r :name)) nyc-people)
; → ("Alice" "Carol")

;; Edad media de todos los contactos
(let ((all (records (query contacts)))
      (ages (map (fn (r) (field-get r :age)) all)))
  (/ (reduce + 0 ages) (length ages)))
; → 30.5

7. Proyecto: Control de gastos

Vamos a construir un control de gastos personal con resúmenes por categoría.

Configurar la tabla

(deftable expenses
  (description:string amount:number category:string date:string))

;; Añadir algunos gastos
(insert expenses {:description "Groceries"     :amount 85.50  :category "food"      :date "2026-02-01"})
(insert expenses {:description "Electric bill"  :amount 120.00 :category "utilities" :date "2026-02-03"})
(insert expenses {:description "Coffee shop"    :amount 12.75  :category "food"      :date "2026-02-05"})
(insert expenses {:description "Bus pass"       :amount 75.00  :category "transport" :date "2026-02-01"})
(insert expenses {:description "Restaurant"     :amount 45.00  :category "food"      :date "2026-02-10"})
(insert expenses {:description "Internet"       :amount 65.00  :category "utilities" :date "2026-02-03"})
(insert expenses {:description "Books"          :amount 32.99  :category "education" :date "2026-02-15"})

Consultar y analizar

;; Gasto total
(let ((all (records (query expenses))))
  (reduce + 0 (map (fn (r) (field-get r :amount)) all)))
; → 436.24

;; Gasto por categoría
(defn category-total (cat)
  (let ((rows (records (query expenses :where "category = ?" :params (list cat)))))
    (reduce + 0 (map (fn (r) (field-get r :amount)) rows))))

(println (str "Food:      $" (category-total "food")))
(println (str "Utilities: $" (category-total "utilities")))
(println (str "Transport: $" (category-total "transport")))
(println (str "Education: $" (category-total "education")))
> Food: $143.25 Utilities: $185 Transport: $75 Education: $32.99

Encontrar los gastos más grandes

;; Los 3 gastos más altos por importe
(query expenses :order "amount" :desc true :limit 3)

8. Proyecto: Creación de una lista de tareas

Un miniproyecto clásico que muestra cómo combinar tablas con funciones auxiliares.

Crear la tabla y las funciones auxiliares

(deftable todos
  (title:string priority:number done:bool))

;; Auxiliar: añadir una tarea
(defn add-todo (title priority)
  (insert todos {:title title :priority priority :done false})
  (println (str "Added: " title)))

;; Auxiliar: marcar como completada
(defn done! (id)
  (update todos id {:done true})
  (println "Done!"))

;; Auxiliar: mostrar tareas pendientes
(defn pending ()
  (query todos :where "done = ?" :params (list false) :order "priority"))

;; Auxiliar: mostrar tareas completadas
(defn completed ()
  (query todos :where "done = ?" :params (list true)))

Usarlo

;; Añadir tareas (número de prioridad más bajo = más importante)
(add-todo "Write README"       1)
(add-todo "Fix login bug"     1)
(add-todo "Update tests"      2)
(add-todo "Refactor CSS"      3)
(add-todo "Deploy to staging" 2)

;; Ver tareas pendientes
(pending)

;; Completar una tarea
(done! 1)  ; marcar "Write README" como completada

;; Ver lo que queda
(pending)

;; Ver lo completado
(completed)
Consejo: Puedes guardar estas definiciones de funciones en un archivo .el y cargarlas con :load todo-helpers.el cada vez que abras el REPL.

9. Proyecto: Libro de recetas con navegación y edición

EELisp tiene dos comandos de vista interactivos: browse muestra una tabla/cuadrícula navegable, y edit abre un formulario CRUD. Vamos a crear un libro de recetas usando ambos.

Crear la tabla

Nuestra tabla de recetas usa varios tipos de campo: string para texto corto, number para cantidades, bool para indicadores y memo para notas más largas.

(deftable recipes
  (name:string
   cuisine:string
   servings:number
   vegetarian:bool
   notes:memo))

Añadir algunas recetas

(insert recipes {:name "Pasta Carbonara"
                 :cuisine "Italian"
                 :servings 4
                 :vegetarian false
                 :notes "Use guanciale, not bacon."})

(insert recipes {:name "Vegetable Curry"
                 :cuisine "Indian"
                 :servings 6
                 :vegetarian true
                 :notes "Toast spices first."})

(insert recipes {:name "Miso Soup"
                 :cuisine "Japanese"
                 :servings 2
                 :vegetarian true
                 :notes "Don't boil the miso."})

Navegar por tus recetas (vista de tabla)

El comando browse abre una tabla/cuadrícula navegable que muestra todos los registros a la vez:

;; Abrir la vista de tabla
(browse recipes)

La vista de tabla muestra todos los registros en una cuadrícula. Haz clic en una fila para seleccionarla, o usa Prev/Next para navegar.

Consejo: También puedes ejecutar browse, edit y defform desde un bloque ```eelisp en tus notas Markdown. Pulsa ⌘⇧Return y el formulario o tabla se abre como un panel lateral interactivo junto al editor — para que puedas ver tus datos y tus notas al mismo tiempo.

Editar con un formulario CRUD

El comando edit abre un formulario estilo dBASE para operaciones CRUD completas:

;; Abrir el formulario CRUD
(edit recipes)

El formulario muestra un registro a la vez. Puedes:

Filtrado y ordenación

Tanto browse como edit admiten :where, :params y :order:

;; Navegar solo las recetas vegetarianas
(browse recipes :where "vegetarian = ?" :params (list true))

;; Editar ordenado por cocina
(edit recipes :order "cuisine")

;; Comparar con la salida de tabla de texto
(query recipes :select (list "name" "cuisine" "servings"))

Almacenamiento persistente

Por defecto, los datos se almacenan en memoria y se pierden al cerrar la aplicación. Para persistir tus recetas:

;; Crear un archivo de base de datos en tu carpeta
:db new recipes

;; Ahora todas las llamadas a deftable/insert/update se persisten
;; Comprobar la base de datos actual
:db

;; Volver a memoria
:db memory
Consejo: Guarda esto como recipes.lisp y cárgalo con :load recipes.lisp. Si tu carpeta tiene un archivo eelisp.db, el REPL se conecta a él automáticamente cuando abres la carpeta.

10. Proyecto: Formularios calculadora con campos computados

El comando defform crea formularios interactivos independientes con campos computados. Los campos computados se actualizan automáticamente cuando cambias los valores de entrada — no se guardan en ninguna base de datos.

Calculadora de interés compuesto

Vamos a crear una calculadora que computa el interés compuesto a partir de tres entradas:

(defform compound-interest
  (principal:number
   rate:number
   years:number)
  :computed
  ((total-amount (* principal (pow (+ 1 (/ rate 100)) years)))
   (interest (- total-amount principal))))

Esto crea un formulario con tres campos editables (principal, rate, years) y dos campos computados (total-amount, interest). Prueba a cambiar los valores — los campos computados se actualizan al instante.

Cómo funciona defform

La sintaxis es:

(defform form-name
  (field1:type field2:type ...)
  :computed
  ((computed1 expression1)
   (computed2 expression2)))

Más ejemplos

Conversor de temperatura:

(defform temperature
  (celsius:number)
  :computed
  ((fahrenheit (+ (* celsius 1.8) 32))
   (kelvin (+ celsius 273.15))))

Calculadora de IMC:

(defform bmi
  (weight-kg:number
   height-cm:number)
  :computed
  ((height-m (/ height-cm 100))
   (bmi (/ weight-kg (* height-m height-m)))))
Consejo: Carga el archivo de ejemplo con :load compound-calculator.lisp para verlo en acción.

11. Proyecto: Agenda personal — Elementos y categorías

EELisp incluye un gestor de información personal inspirado en Lotus Agenda construido sobre la capa de base de datos. Piensa en él como una lista de tareas programable combinada con un organizador de conocimiento — escribes texto libre y la estructura emerge de las categorías.

Añadir tus primeros elementos

Los elementos son texto ante todo. Simplemente escribe lo que tengas en mente:

;; Elemento simple
(add-item "Finish quarterly report for Sarah")

;; Con fecha de vencimiento y prioridad
(add-item "Call dentist to reschedule"
  :when "2026-03-01" :priority 2)

;; Con una categoría y notas
(add-item "Buy groceries"
  :category "personal"
  :notes "milk, eggs, bread, coffee")

;; Elemento de trabajo de alta prioridad
(add-item "Deploy v2.1 to staging"
  :when "2026-02-26" :priority 1)

;; Una reunión
(add-item "Team standup" :when "2026-02-24" :priority 3)
> (add-item "Finish quarterly report for Sarah") Item #1: Finish quarterly report for Sarah > (add-item "Call dentist to reschedule" :when "2026-03-01" :priority 2) Item #2: Call dentist to reschedule When: 2026-03-01 Priority: 2

Explora tus elementos

El comando items muestra todos los elementos en una vista de tabla:

;; Ver todos los elementos
(items)
> (items) id | text | when | priority | categories ---+-----------------------------------+------------+----------+----------- 1 | Finish quarterly report for Sarah | | | 2 | Call dentist to reschedule | 2026-03-01 | 2 | 3 | Buy groceries | | | personal 4 | Deploy v2.1 to staging | 2026-02-26 | 1 | 5 | Team standup | 2026-02-24 | 3 | 5 record(s)

Define categorías

Las categorías son jerárquicas — usa separadores /. También puedes definir grupos exclusivos donde un elemento solo puede pertenecer a un hijo:

;; Categorías de trabajo
(defcategory work)
(defcategory work/projects)
(defcategory work/meetings)
(defcategory work/admin)

;; Categorías personales
(defcategory personal)
(defcategory personal/health)
(defcategory personal/errands)

;; Grupo exclusivo: el elemento solo puede ser high, medium O low
(defcategory priority :exclusive true
  :children (high medium low))

;; Ver el árbol
(categories)
> (categories) personal personal/errands personal/health priority [exclusive] priority/high priority/low priority/medium work work/admin work/meetings work/projects

Asigna categorías a los elementos

Usa assign para etiquetar elementos. Las categorías exclusivas eliminan automáticamente los hermanos:

;; Etiquetar elementos con categorías
(assign 1 "work/projects")
(assign 2 "personal/health")
(assign 4 "work/projects")
(assign 5 "work/meetings")

;; Establecer prioridades
(assign 1 "priority/high")
(assign 4 "priority/high")
(assign 2 "priority/medium")

;; Comprobar un elemento
(item-get 1)
> (item-get 1) Item #1: Finish quarterly report for Sarah Categories: priority/high, work/projects

Filtrar y buscar

;; Encontrar todos los elementos de trabajo
(items :category "work")

;; Buscar por texto
(items :search "quarterly")

;; Solo elementos con prioridad 1
(items :priority 1)

;; Elementos con fecha anterior a marzo
(items :when-before "2026-03-01")

;; ¿Cuántos elementos tengo?
(item-count)

Actualizar y completar elementos

;; Actualizar propiedades
(item-set 1 :priority 1 :when "2026-02-28")

;; Cambiar prioridad — ¡exclusiva! Elimina "high" automáticamente
(assign 1 "priority/low")
(item-get 1)  ; ahora muestra priority/low, no priority/high

;; Marcar elemento como hecho (eliminación suave)
(item-done 3)  ; "Buy groceries" — ¡hecho!
(item-count)     ; → 4 (eran 5)

;; Eliminar una categoría
(unassign 2 "personal/health")
Cómo funciona: Los elementos y las categorías se almacenan en tablas SQLite (_items, _categories, _rules), creadas automáticamente. Esto significa que tu agenda persiste si usas un archivo de base de datos (:db myagenda.db). Las categorías son arrays JSON dentro de cada registro de elemento, y las propiedades (when, priority, etc.) se almacenan como un objeto JSON.
Consejo: Combina los elementos de la agenda con la capa de base de datos. Podrías escribir un helper que consulte tus elementos y genere un archivo de resumen diario en Markdown.

12. Reglas de Auto-Categorización — La magia de Lotus Agenda

Define reglas que categorizan automáticamente los elementos según su contenido. Esto es lo que hizo revolucionario a Lotus Agenda en 1988 — y ahora está en EELisp.

Define reglas de coincidencia de texto

;; Regla: los elementos que contienen "URGENT" obtienen prioridad alta
(defrule urgent-flag
  :when (str-contains text "URGENT")
  :assign "priority/high")

;; Regla: reuniones y llamadas → work/meetings
(defrule meeting-detect
  :when (or (str-contains text "meeting")
            (str-contains text "call with")
            (str-contains text "standup"))
  :assign "work/meetings")

;; Regla: elementos de compras → personal/errands
(defrule errand-detect
  :when (or (str-contains text "buy ")
            (str-contains text "groceries"))
  :assign "personal/errands")
Reglas definidas: urgent-flag, meeting-detect, errand-detect

Aplica reglas a los elementos existentes

;; Aplicar reglas a todos los elementos (devuelve el número de cambios)
(apply-rules)
;; → 3 (elementos que coincidieron con las reglas)

;; Aplicar a un solo elemento
(apply-rules 2)

;; Comprobar los resultados
(item-get 2)
;; → ahora muestra Categories: priority/high (si contenía "URGENT")

Activa la auto-categorización

;; Activar auto-aplicación — las reglas se ejecutan en cada add-item
(auto-categorize true)

;; ¡Ahora los nuevos elementos se categorizan automáticamente!
(add-item "URGENT: Deploy hotfix to production")
;; → asignado automáticamente a priority/high

(add-item "Buy milk and eggs this weekend")
;; → asignado automáticamente a personal/errands

(add-item "Standup with the team at 9am")
;; → asignado automáticamente a work/meetings

Usa regex para extracción de fechas

;; Extraer fechas ISO del texto del elemento usando :action
(defrule date-extract
  :when (str-matches text "\\b(\\d{4}-\\d{2}-\\d{2})\\b")
  :action (item-set id :when (match 1)))

;; Los elementos con fechas obtienen :when automáticamente
(add-item "Deadline is 2026-04-01")
(item-get 8)
;; → When: 2026-04-01
str-matches devuelve grupos de captura, y (match 1) extrae el primer grupo.

Gestiona tus reglas

;; Listar todas las reglas
(rules)
;; → urgent-flag: (str-contains text "URGENT")
;;   meeting-detect: (or (str-contains text "meeting") ...)
;;   errand-detect: (or (str-contains text "buy ") ...)
;;   date-extract: (str-matches text "\\b(\\d{4}-\\d{2}-\\d{2})\\b")

;; Eliminar una regla que ya no necesitas
(drop-rule "errand-detect")

;; Desactivar la auto-categorización
(auto-categorize false)
Cómo funciona: Las condiciones de las reglas se almacenan como expresiones Lisp serializadas en la tabla _rules. Al evaluarse, los campos de cada elemento (text, notes, id, categories y todas las propiedades) se vinculan como símbolos en el entorno de evaluación de la regla. El atajo :assign genera acciones (assign id "category"), mientras que :action te permite ejecutar cualquier expresión Lisp.
Consejo: Las reglas son expresiones Lisp completas — combina and/or/not, usa has-category para comprobar asignaciones existentes, (get :property) para pruebas de propiedades, y str-matches para patrones regex. Puedes construir lógica de categorización arbitrariamente compleja.

13. Vistas — Perspectivas Dinámicas

Las vistas son consultas guardadas que filtran, ordenan y agrupan tus elementos. Define una vista una vez y usa show en cualquier momento para ver los últimos elementos que coincidan — como un panel de control en vivo para tu agenda.

Define tus primeras vistas

Las vistas usan expresiones de filtro — el mismo contexto que las reglas (text, categories, propiedades, has-category, overdue?):

;; Un tablero de todos los elementos de trabajo, agrupados por categoría
(defview work-board
  :source items
  :filter (has-category "work")
  :group-by category
  :sort-by when)

;; Solo elementos de alta prioridad
(defview urgent
  :source items
  :filter (= priority "1")
  :sort-by when)

;; Elementos sin categorías (bandeja de entrada sin categorizar)
(defview inbox
  :source items
  :filter (= (length categories) 0))

;; Elementos vencidos
(defview overdue
  :source items
  :filter (overdue?)
  :sort-by when)

Muestra una vista

Usa show para mostrar los resultados de una vista. Las vistas agrupadas se representan como un árbol con encabezados de categoría:

;; Mostrar el tablero de trabajo
(show work-board)
> (show work-board) ▸ work/meetings (2) Team standup Call with client ▸ work/projects (3) Deploy v2.1 to staging Finish quarterly report Code review PR #42
;; Mostrar elementos urgentes
(show urgent)
> (show urgent) id | text | when | priority ---+-----------------------------------+------------+--------- 4 | Deploy v2.1 to staging | 2026-02-26 | 1 6 | URGENT: Deploy hotfix | | 1 2 record(s)

Usa helpers de fecha para vistas basadas en tiempo

EELisp proporciona helpers de fecha que son perfectos para filtros de vistas:

;; La fecha de hoy como cadena ISO
(today)
; → "2026-02-21"

;; Sumar días, semanas o meses
(date-add (today) 7 :days)
; → "2026-02-28"

(date-add (today) 1 :months)
; → "2026-03-21"

;; Diferencia en días
(date-diff "2026-02-21" "2026-03-01")
; → 8

Gestiona tus vistas

;; Listar todas las vistas definidas
(views)
;; → work-board: filter=(has-category "work") group-by=category sort-by=when
;;   urgent:     filter=(= priority "1") sort-by=when
;;   inbox:      filter=(= (length categories) 0)
;;   overdue:    filter=(overdue?) sort-by=when

;; Eliminar una vista
(drop-view "inbox")
Cómo funciona: Las vistas se almacenan en la tabla _views con sus expresiones de filtro serializadas como Lisp. Cuando usas show en una vista, obtiene todos los elementos, evalúa el filtro en cada uno (vinculando todos los campos del elemento como símbolos), ordena las coincidencias y opcionalmente las agrupa. Los elementos con propiedades faltantes se excluyen silenciosamente — sin errores.
Consejo: Usa el helper overdue? en filtros para encontrar elementos pasados de su fecha :when. Combina con and/or para vistas complejas: :filter (and (has-category "work") (overdue?)) muestra los elementos de trabajo vencidos.

14. Integración con Calendario

Conecta tu agenda con fechas del calendario. Visualiza lo que está programado para un día específico, navega por un rango de fechas y añade elementos rápidamente para hoy — todo desde el REPL.

Ver elementos por fecha

Usa items-on para ver todo lo programado para una fecha específica:

;; ¿Qué hay para el lunes?
(items-on "2026-02-24")

;; Usa (today) para fechas dinámicas
(items-on (today))
> (items-on "2026-02-24") id | text | when | priority | categories ---+-----------------------------------+------------+----------+----------- 5 | Team standup | 2026-02-24 | 3 | work/meetings 1 record(s)

Navega por un rango de fechas

Usa items-between para ver los elementos en un rango de fechas (inclusivo en ambos extremos):

;; ¿Qué hay esta semana?
(items-between "2026-02-24" "2026-02-28")

;; Próximos 7 días usando helpers de fecha
(items-between (today) (date-add (today) 7 :days))

;; Todo el mes
(items-between "2026-02-01" "2026-02-28")
> (items-between "2026-02-24" "2026-02-28") id | text | when | priority | categories ---+-----------------------------------+------------+----------+----------- 5 | Team standup | 2026-02-24 | 3 | work/meetings 4 | Deploy v2.1 to staging | 2026-02-26 | 1 | work/projects 1 | Finish quarterly report | 2026-02-28 | | work/projects 3 record(s)

Añadir elementos rápidamente para hoy

add-item-today crea un elemento con :when establecido automáticamente a la fecha de hoy:

;; Añadir rápidamente para hoy
(add-item-today "Review pull requests" :priority 1)
(add-item-today "Quick grocery run" :notes "milk, bread")
(add-item-today "Team sync" :category "work/meetings")
> (add-item-today "Review pull requests" :priority 1) Item #9: Review pull requests When: 2026-02-21 Priority: 1

Combinar con helpers de fecha y vistas

Los helpers de fecha y los comandos de calendario funcionan muy bien junto con las vistas:

;; La fecha de hoy como cadena ISO
(today)               ; → "2026-02-21"

;; Aritmética de fechas
(date-add (today) 7 :days)     ; → "2026-02-28"
(date-add (today) 1 :months)   ; → "2026-03-21"
(date-diff "2026-02-21" "2026-03-01")  ; → 8 (días)

;; Definir una vista para los elementos de esta semana
(defview this-week
  :source items
  :filter (and (>= when (today))
              (<= when (date-add (today) 7 :days)))
  :sort-by when)
Consejo: add-item-today es un atajo para (add-item text :when (today)). Admite las mismas opciones :priority, :category y :notes que add-item. Combínalo con las reglas de auto-categorización para una organización sin esfuerzo.
Referencia de comandos de calendario:

15. Entrada Inteligente & HTTP

El comando add utiliza análisis de lenguaje natural basado en regex para extraer automáticamente fechas, prioridades y personas de tu texto. Combinado con las funciones integradas de HTTP y JSON, EELisp se convierte en una potente herramienta de automatización.

Creación de elementos con lenguaje natural

Simplemente escribe lo que tengas en mente — add descifra los metadatos:

;; Las fechas se extraen automáticamente
(add "Meet Alice tomorrow for coffee")
;; → :when = fecha de mañana

(add "Call Bob next Monday about the project")
;; → :when = fecha del próximo lunes, :who = Bob

(add "Review docs end of month")
;; → :when = último día del mes actual

(add "Deploy hotfix in 3 days")
;; → :when = dentro de 3 días
> (add "Meet Alice tomorrow for coffee") Item #1: Meet Alice for coffee When: 2026-02-23 Who: Alice

Detección de prioridad

Expresa urgencia con palabras o signos de exclamación:

;; Prioridad basada en palabras
(add "URGENT fix server crash")
;; → :priority 1

;; Signos de exclamación: !!! = 1, !! = 2, ! = 3
(add "Review PR !!")
;; → :priority 2

(add "Update README low priority")
;; → :priority 4

Personas y @menciones

Las personas se detectan a partir de @menciones, preposiciones y verbos de acción:

;; @menciones
(add "Review code for @carlos")
;; → :who = carlos

;; Patrones Verbo + Nombre
(add "Email Sarah March 15 about renewal")
;; → :who = Sarah, :when = March 15

;; Patrones Preposición + Nombre
(add "Meeting with John this weekend")
;; → :who = John, :when = Saturday

Vista previa con smart-parse

Usa smart-parse para ver qué se extraería sin crear un elemento:

(smart-parse "Meet Alice tomorrow for coffee !!")
> (smart-parse "Meet Alice tomorrow for coffee !!") {:text "Meet Alice for coffee" :when "2026-02-23" :priority 2 :who ("Alice")}

Funciones integradas de HTTP y JSON

EELisp incluye funciones genéricas de HTTP y JSON para integración con APIs:

;; Parsear JSON a datos EELisp
(json-parse "{\"name\": \"Alice\", \"scores\": [95, 87]}")
;; → {:name "Alice" :scores (95 87)}

;; Convertir EELisp a JSON
(json-stringify {:name "Bob" :active true})
;; → "{\"active\":true,\"name\":\"Bob\"}"

;; Peticiones HTTP
(http-get "https://api.example.com/data")
;; → {:status 200 :body "..."}

(http-post "https://api.example.com/items"
  (json-stringify {:title "New item"})
  :content-type "application/json")
;; → {:status 201 :body "..."}
Patrones de fecha reconocidos: Fechas ISO (2026-03-15), tomorrow/today/yesterday, next Monday, this weekend, in 3 days, in 2 weeks, end of week, end of month, March 15.
Consejo: add funciona perfectamente con las reglas de auto-categorización. Después de que smart-parsing extrae los metadatos, las reglas siguen ejecutándose — así que (add "URGENT meeting with Bob tomorrow") puede simultáneamente establecer la fecha, la prioridad y activar tu regla "meeting-detect" para asignar work/meetings.

16. Elementos Recurrentes & Plantillas

Elementos que se repiten según un calendario y plantillas reutilizables para tareas comunes.

Paso 1 — Crear un elemento recurrente

Añade :recur a cualquier llamada add-item. Patrones integrados: :daily, :weekly, :monthly.

(add-item "Team standup" :when "2026-02-24" :recur :weekly :category "work")
;; Item #1: Team standup
;;   When: 2026-02-24
;;   Recurrence: weekly
;;   Categories: work

Paso 2 — Intervalos personalizados

Usa (every N :unit) para patrones de recurrencia personalizados:

(add-item "Quarterly review" :when "2026-04-01" :recur (every 3 :months))
(add-item "Biweekly report" :when "2026-02-28" :recur (every 2 :weeks))

Paso 3 — Completar un elemento recurrente

Cuando marcas un elemento recurrente como hecho, el sistema crea automáticamente la siguiente ocurrencia:

(item-done 1)
;; Item #4: Team standup
;;   When: 2026-03-03          ← avanzado 7 días
;;   Recurrence: weekly
;;   Categories: work
Consejo: El nuevo elemento conserva todas las propiedades — texto, categorías, prioridad, patrón de recurrencia y notas. Solo la fecha avanza.

Paso 4 — Definir una plantilla

Las plantillas son planos reutilizables para elementos que creas con frecuencia:

(deftemplate weekly-review
  :text "Weekly review: reflect on goals"
  :category "work/admin"
  :priority 2
  :notes "1. What went well?\n2. What to improve?")
;; "Template 'weekly-review' defined"

Paso 5 — Crear elementos a partir de plantillas

Usa from-template con sobrecargas opcionales:

(from-template weekly-review :when "2026-03-07")
;; Item #5: Weekly review: reflect on goals
;;   When: 2026-03-07
;;   Priority: 2
;;   Categories: work/admin

;; Sobrescribir cualquier campo
(from-template weekly-review :when "2026-03-14" :priority 1)

Paso 6 — Gestionar plantillas

Listar y eliminar plantillas:

(templates)
;; weekly-review — "Weekly review: reflect on goals" [work/admin] !2

(drop-template weekly-review)
;; "Template 'weekly-review' removed"
Consejo: Las plantillas también pueden incluir :recur — p. ej. (deftemplate standup :text "Daily standup" :recur :daily). Cada elemento creado a partir de ella será automáticamente recurrente.

17. Múltiples Agendas

Gestiona agendas separadas para trabajo, personal y proyectos. Cada agenda es una base de datos SQLite independiente con sus propios elementos, categorías, reglas, vistas y plantillas.

Paso 1: Abrir una segunda agenda

Usa open-agenda para crear un nuevo archivo de base de datos y cambiar a él:

;; Tu agenda predeterminada ya está activa
(add-item "Personal task" :category "personal")

;; Abrir una agenda de trabajo — crea el archivo si no existe
(open-agenda "work.db")
;; "Opened agenda 'work' at /path/to/work.db"

(add-item "Deploy to staging" :when "2026-03-01" :priority 1)

Paso 2: Cambiar entre agendas

Usa use-agenda para cambiar. Los elementos están completamente aislados entre agendas:

;; Volver a la predeterminada
(use-agenda memory)
(items)  ;; → solo "Personal task"

;; Cambiar a trabajo
(use-agenda work)
(items)  ;; → solo "Deploy to staging"

Paso 3: Listar y gestionar agendas

;; Ver todas las agendas abiertas
(agendas)
;; Agendas:
;;   memory — :memory:
;;   work [active] — /path/to/work.db

;; Cerrar una agenda que ya no necesitas
(close-agenda work)

Paso 4: Exportar e importar

Haz una copia de seguridad de tu agenda a JSON, o transfiere elementos entre agendas:

;; Exportar la agenda actual
(export-agenda "memory" :format :json :path "backup.json")
;; "Exported agenda 'memory' to /path/to/backup.json"

;; Importar a una agenda diferente
(open-agenda "archive.db")
(import-agenda "backup.json")
;; "Imported: 15 items, 4 categories, 3 rules, 2 views, 1 templates"
Consejo: Los nombres de las agendas se derivan de los nombres de archivo — work.db se convierte en work, :memory: se convierte en memory. Usa nombres de archivo descriptivos para facilitar el cambio.

18. Barra Lateral de Agenda & Integración con Calendario

La barra lateral de EEditor tiene tres modos: Archivos, Calendario y Agenda. Los dos últimos se integran directamente con tu base de datos de agenda EELisp, dándote una visión general visual sin necesidad de escribir consultas.

La Barra Lateral de Agenda

Haz clic en el icono del portapapeles en el control segmentado de la barra lateral para cambiar al modo Agenda. Consulta automáticamente tu base de datos y organiza los elementos en tres secciones:

Cada elemento muestra un punto de prioridad (rojo / naranja / amarillo / gris), el texto del elemento, las categorías y una fecha formateada. Pulsa el botón de actualizar para recargar después de añadir elementos en el REPL.

Cómo funciona entre bastidores

La barra lateral de agenda no duplica la lógica de la base de datos. En su lugar, el AgendaViewModel llama a interpreter.eval() con las mismas funciones integradas de EELisp que usarías en el REPL:

;; Lo que la barra lateral de Agenda ejecuta internamente:
(items :when-before "2026-02-23")    ;; → Sección Vencidos
(items-on "2026-02-23")               ;; → Sección Hoy
(items-between "2026-02-24" "2026-03-02") ;; → Sección Próximos

Calendario + Agenda

La barra lateral de Calendario también se integra con tu agenda. Cada celda de día muestra indicadores de doble punto:

Cuando haces clic en un día, el panel de detalle muestra tanto la sección de Archivos como la de Agenda, para que puedas ver tu actividad de escritura junto con tus tareas programadas.

Pruébalo tú mismo

;; 1. Añade algunos elementos con fechas
(add-item "Review PR" :when (today) :priority 1)
(add-item "Team meeting" :when (date-add (today) 1 :days) :category "work/meetings")
(add-item "Deploy v2" :when (date-add (today) 3 :days) :priority 2)
(add-item "Overdue task" :when "2026-01-15")

;; 2. Cambia a la pestaña de Agenda en la barra lateral
;;    → Verás:  Vencidos (1) | Hoy (1) | Próximos 7 Días (2)

;; 3. Cambia a la pestaña de Calendario en la barra lateral
;;    → Hoy tiene un punto naranja; haz clic para ver el elemento
Consejo: La barra lateral de la interfaz gráfica y el REPL comparten el mismo intérprete y base de datos. Cualquier elemento que añadas en el REPL aparece en la barra lateral tras actualizar, y viceversa. Este es el poder de la integración EELisp-GUI — un motor de datos, múltiples vistas.

19. Consejos & Patrones

Pipe para legibilidad

Usa la macro pipe para encadenar operaciones de izquierda a derecha en lugar de anidarlas:

;; En lugar de esta expresión profundamente anidada:
(str-join (map str-upper (filter (fn (s) (str-starts-with s "a")) (str-split "apple,avocado,banana,apricot" ","))) ", ")

;; Escribe esto:
(pipe "apple,avocado,banana,apricot"
  (str-split ",")
  (filter (fn (s) (str-starts-with s "a")))
  (map str-upper)
  (str-join ", "))
; → "APPLE, AVOCADO, APRICOT"

Guardar y cargar scripts

Escribe tus funciones en un archivo y cárgalas en el REPL:

;; En el REPL, carga un archivo de script:
:load ~/scripts/finance.el

;; O haz clic derecho en un archivo .el en la barra lateral y
;; selecciona "Run in REPL"

Hoja de referencia de comandos del REPL

ComandoDescripción
:helpMostrar todos los comandos del REPL
:load <file>Cargar y ejecutar un script
:dbMostrar la ruta de la base de datos actual
:db <path>Cambiar a un archivo de base de datos
:db new <name>Crear un nuevo archivo de base de datos
:db memoryCambiar a base de datos en memoria
:envListar todos los símbolos definidos
:clearLimpiar la salida
:resetReiniciar el intérprete
(tables)Listar todas las tablas en la base de datos actual
(describe tablename)Mostrar el esquema de una tabla
(drop-table tablename)Eliminar una tabla

Patrones comunes

PatrónEjemplo
Transformar una lista(map inc numbers)
Filtrar una lista(filter even? numbers)
Agregar una lista(reduce + 0 numbers)
Consultar + procesar(map (fn (r) (field-get r :name)) (records (query ...)))
Condicional(if (> x 0) "positive" "non-positive")
Ámbito local(let ((x 1) (y 2)) (+ x y))
Bucle(loop ((i 0)) (= i 10) (recur (inc i)))
Matemáticas(pow 2 10), (round 3.14159 2), (abs -5)
Vista de tabla(browse tasks)
Formulario CRUD(edit tasks)
Formulario calculadora(defform name (fields) :computed ((name expr)))
Añadir elemento de agenda(add-item "text" :when "date" :priority n)
Explorar elementos(items :category "work" :search "text")
Definir categoría(defcategory work/projects)
Asignar categoría(assign 1 "work/projects")
Completar elemento(item-done 1)
Definir regla(defrule name :when (str-contains text "URGENT") :assign "priority/high")
Auto-categorizar(auto-categorize true)
Aplicar reglas(apply-rules)
Regla regex(defrule name :when (str-matches text "pattern") :action expr)
Definir vista(defview name :source items :filter (has-category "work") :group-by category)
Mostrar vista(show work-board)
Fecha de hoy(today), (date-add (today) 7 :days)
Elementos para una fecha(items-on "2026-02-24")
Elementos en rango(items-between (today) (date-add (today) 7 :days))
Añadir rápido hoy(add-item-today "text" :priority 1)
Añadir inteligente (NLP)(add "Meet Alice tomorrow !!")
Vista previa de análisis(smart-parse "Call Bob next Monday")
Parsear JSON(json-parse "{\"a\": 1}")
Convertir a JSON(json-stringify {:name "Alice"})
HTTP GET(http-get "https://api.example.com/data")
Elemento recurrente(add-item "Task" :when "2026-03-01" :recur :weekly)
Intervalo personalizado(add-item "Review" :when "2026-04-01" :recur (every 3 :months))
Definir plantilla(deftemplate standup :text "Standup" :recur :daily)
Usar plantilla(from-template standup :when "2026-03-01")
Abrir agenda(open-agenda "work.db")
Cambiar agenda(use-agenda work)
Listar agendas(agendas)
Exportar agenda(export-agenda "work" :format :json :path "backup.json")
Importar agenda(import-agenda "backup.json")
¿Qué sigue? Prueba a combinar tablas y funciones para construir tus propias herramientas: un registro de lecturas, un libro de recetas, un seguimiento de entrenamientos o un CRM personal. El REPL es tu zona de juegos.