#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 03 — La Bitácora: la memoria que se escribe sola ================================================ Episodio «¿Cómo se le da memoria a una IA?» — Punto 5 del guion, «El diario que se escribe solo» (acto III — El relato y lo que lo sostiene), la Capa E. La idea del episodio -------------------- La quinta pregunta no es «qué hiciste», es «¿cómo fue el día?». No pide una lista: pide un **relato**. Y lo mejor es que **se escribe sola**: una vez al día, de madrugada, el asistente coge lo del día (los hechos, los mensajes y el diario de ayer para no repetirse) y escribe **una entrada por día**. Con un detalle de otro nivel: puede decidir que **ese día no hay nada que contar**. Ese «no escribo» no es un error; es una respuesta válida. Qué hace este ejemplo --------------------- Lee un fichero con el «material del día» y hace lo mismo que haría Valet: * Si el día trae algo que merece memoria → escribe una entrada en `bitacora/AAAA-MM-DD.md`, en primera persona, y le añade los matices (ánimo, logros, dificultades, etiquetas). * Si el día está vacío → **no escribe nada** y lo dice en voz alta. Y respeta la regla de oro: **una sola entrada por día**. Si ya existe, no la duplica. En Valet el relato lo escribe un modelo. Aquí lo montamos a base de plantilla, sin instalar nada, porque lo que queremos enseñar no es la prosa, sino el **mecanismo**: recoger el día, decidir, y escribir una única entrada. Uso: python3 03-la-bitacora.py # enseña los días de ejemplo python3 03-la-bitacora.py datos/dia-2026-10-18.json """ import json import os import sys # Dónde se guardan las entradas, como en un diario real: un fichero por día. CARPETA_BITACORA = "bitacora" def dia_vacio(material): """Un día sin hechos, sin mensajes y sin nada que contar no merece entrada.""" return not material.get("hechos") and not material.get("mensajes") def componer_relato(material): """ Escribe el relato del día, en primera persona y con nuestras palabras. El episodio lo pide así: «no enumeres los hechos otra vez, descríbelos con tus palabras. Describe en vez de diagnosticar.» Aquí, sin modelo, hacemos una versión sencilla de esa idea: unimos los hechos en un párrafo que suene a cómo lo contarías tú, no a un informe. """ hechos = material.get("hechos", []) frases = [] # Abrimos con el primer hecho como arranque del día. if hechos: frases.append("Hoy " + hechos[0].rstrip(".").lower() + ".") # Y seguimos con el resto, enganchándolos con naturalidad. for hecho in hechos[1:]: frases.append("También " + hecho.rstrip(".").lower() + ".") if material.get("mensajes"): frases.append("Entre medias he ido hablando con el asistente de lo de siempre.") return " ".join(frases) def escribir_entrada(ruta, material): fecha = material["fecha"] os.makedirs(CARPETA_BITACORA, exist_ok=True) destino = os.path.join(CARPETA_BITACORA, f"{fecha}.md") # Regla de oro: una sola entrada por día. if os.path.exists(destino): print(f" {fecha}: ya había una entrada, no se duplica → {destino}") return relato = componer_relato(material) animo = material.get("animo", "sin determinar") logros = material.get("logros", []) dificultades = material.get("dificultades", []) etiquetas = material.get("etiquetas", []) lineas = [ f"# {fecha}", "", relato, "", f"- **Ánimo:** {animo}", ] if logros: lineas.append("- **Logros:** " + "; ".join(logros)) if dificultades: lineas.append("- **Dificultades:** " + "; ".join(dificultades)) if etiquetas: lineas.append("- **Etiquetas:** " + ", ".join(etiquetas)) lineas.append("") with open(destino, "w", encoding="utf-8") as f: f.write("\n".join(lineas)) print(f" {fecha}: se escribe la entrada → {destino}") print(" " + "-" * 62) for linea in lineas: print(" " + linea) print(" " + "-" * 62) def procesar(material): fecha = material.get("fecha", "sin fecha") print(f"\n Material del día {fecha}:") print(f" hechos : {len(material.get('hechos', []))}") print(f" mensajes : {len(material.get('mensajes', []))}") if dia_vacio(material): # Este es el «no escribo» explícito: una respuesta válida, no un error. print(" → NO se escribe entrada: ese día no hay nada que merezca memoria.") return escribir_entrada(CARPETA_BITACORA, material) def main(): print("=" * 68) print(" LA BITÁCORA: LA MEMORIA QUE SE ESCRIBE SOLA") print("=" * 68) if len(sys.argv) > 1: with open(sys.argv[1], encoding="utf-8") as f: procesar(json.load(f)) else: # Sin argumentos, enseña los días de ejemplo que trae el episodio: # dos jornadas reales (18 y 20 de octubre) y un día vacío, para ver # el «no escribo» en acción. aqui = os.path.dirname(os.path.abspath(__file__)) carpeta = os.path.join(aqui, "datos") for nombre in sorted(os.listdir(carpeta)): if nombre.startswith("dia-") and nombre.endswith(".json"): with open(os.path.join(carpeta, nombre), encoding="utf-8") as f: procesar(json.load(f)) print("\n Y así, sin que se lo pidas, el asistente te lleva el diario.") print(" De madrugada, una vez al día, decide y escribe. O decide no escribir.\n") if __name__ == "__main__": main()