Se añadió la creación del directorio `adjuntos/cronologia` en `x1.py` para almacenar imágenes de correos electrónicos. Se actualizó la función `procesar_eml` para manejar imágenes inline y adjuntas, y se refactorizó el código en `email_parser.py` para incluir la lógica de incrustación de imágenes en Markdown. Además, se mejoró la documentación en `MemoriaDeEvolucion.md` para reflejar estos cambios y se optimizó el manejo de errores en varias funciones.
This commit is contained in:
parent
59cb4f4063
commit
fc85347a43
|
@ -38,4 +38,21 @@ Salida Markdown: Escribe el índice seguido del contenido formateado en Markdown
|
||||||
- Se corrigen lints (E402 y líneas largas) sin alterar lógica.
|
- Se corrigen lints (E402 y líneas largas) sin alterar lógica.
|
||||||
- Impacto:
|
- Impacto:
|
||||||
- Los `work_dir.json` existentes deben actualizar la clave a `input_directory`.
|
- Los `work_dir.json` existentes deben actualizar la clave a `input_directory`.
|
||||||
- No hay cambios en claves de `level2` (`cronologia_file`, `attachments_dir`).
|
- No hay cambios en claves de `level2` (`cronologia_file`, `attachments_dir`).
|
||||||
|
|
||||||
|
## 2025-08-08 — Manejo de imágenes (inline y adjuntas) y embebido en Markdown
|
||||||
|
|
||||||
|
- Decisión:
|
||||||
|
- Capturar imágenes tanto adjuntas (`attachment`) como inline (`inline`/sin `Content-Disposition`).
|
||||||
|
- Guardar las imágenes en el directorio de adjuntos configurado y además copiar a `adjuntos/cronologia` dentro del `working_directory`.
|
||||||
|
- Incrustar en el Markdown enlaces de Obsidian con ruta absoluta al archivo copiado en `adjuntos/cronologia` usando la sintaxis de embed `![[...]]` bajo una sección `### Imágenes` por mensaje.
|
||||||
|
- Cambios:
|
||||||
|
- `utils/attachment_handler.py`: nueva función `guardar_imagen` que genera nombres a partir de `Content-ID` o hash y evita colisiones por contenido; refactor de hashing de contenido.
|
||||||
|
- `utils/email_parser.py`:
|
||||||
|
- Se amplía la firma de `procesar_eml`/`procesar_eml_interno` para recibir `dir_adjuntos_cronologia` y copiar allí las imágenes.
|
||||||
|
- Se manejan imágenes en partes `attachment` y `inline`, agregando su ruta absoluta copiada a `mensaje.imagenes_cronologia`.
|
||||||
|
- `models/mensaje_email.py`: `to_markdown()` agrega sección `### Imágenes` con `![[ruta_absoluta]]` previo a `### Adjuntos`.
|
||||||
|
- `x1.py`: crea `adjuntos/cronologia` y pasa la ruta al parser.
|
||||||
|
- Impacto:
|
||||||
|
- El `.md` resultante muestra las imágenes embebidas (Obsidian) desde rutas absolutas bajo `.../adjuntos/cronologia/...`.
|
||||||
|
- Se preserva el listado de adjuntos como enlaces `[[archivo]]`.
|
|
@ -6,7 +6,14 @@ from email.utils import parseaddr, parsedate_to_datetime
|
||||||
|
|
||||||
|
|
||||||
class MensajeEmail:
|
class MensajeEmail:
|
||||||
def __init__(self, remitente, fecha, contenido, subject=None, adjuntos=None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
remitente,
|
||||||
|
fecha,
|
||||||
|
contenido,
|
||||||
|
subject=None,
|
||||||
|
adjuntos=None,
|
||||||
|
):
|
||||||
self.remitente = self._estandarizar_remitente(remitente)
|
self.remitente = self._estandarizar_remitente(remitente)
|
||||||
self.fecha = self._estandarizar_fecha(fecha)
|
self.fecha = self._estandarizar_fecha(fecha)
|
||||||
self.subject = subject if subject else "Sin Asunto"
|
self.subject = subject if subject else "Sin Asunto"
|
||||||
|
@ -37,17 +44,26 @@ class MensajeEmail:
|
||||||
for line in lines:
|
for line in lines:
|
||||||
# Skip metadata lines
|
# Skip metadata lines
|
||||||
if line.strip().startswith(
|
if line.strip().startswith(
|
||||||
("Da: ", "Inviato: ", "A: ", "From: ", "Sent: ", "To: ")
|
(
|
||||||
|
"Da: ",
|
||||||
|
"Inviato: ",
|
||||||
|
"A: ",
|
||||||
|
"From: ",
|
||||||
|
"Sent: ",
|
||||||
|
"To: ",
|
||||||
|
)
|
||||||
) or line.strip().startswith("Oggetto: "):
|
) or line.strip().startswith("Oggetto: "):
|
||||||
continue
|
continue
|
||||||
# Limpiar espacios múltiples dentro de cada línea, pero mantener la línea completa
|
# Limpiar espacios múltiples dentro de cada línea, manteniendo
|
||||||
|
# la línea completa
|
||||||
cleaned_line = re.sub(r" +", " ", line)
|
cleaned_line = re.sub(r" +", " ", line)
|
||||||
cleaned_lines.append(cleaned_line)
|
cleaned_lines.append(cleaned_line)
|
||||||
|
|
||||||
# Unir las líneas preservando los saltos de línea
|
# Unir las líneas preservando los saltos de línea
|
||||||
text = "\n".join(cleaned_lines)
|
text = "\n".join(cleaned_lines)
|
||||||
|
|
||||||
# Limpiar la combinación específica de CRLF+NBSP+CRLF
|
# Limpiar la combinación específica de
|
||||||
|
# CRLF + NBSP + CRLF
|
||||||
text = re.sub(r"\r?\n\xa0\r?\n", "\n", text)
|
text = re.sub(r"\r?\n\xa0\r?\n", "\n", text)
|
||||||
|
|
||||||
# Reemplazar CRLF por LF
|
# Reemplazar CRLF por LF
|
||||||
|
@ -91,7 +107,10 @@ class MensajeEmail:
|
||||||
"""
|
"""
|
||||||
fecha_formato = self.fecha.strftime("%d-%m-%Y")
|
fecha_formato = self.fecha.strftime("%d-%m-%Y")
|
||||||
subject_link = self._formatear_subject_para_link(self.subject)
|
subject_link = self._formatear_subject_para_link(self.subject)
|
||||||
return f"- {fecha_formato} - {self.remitente} - [[cronologia#{self.subject}|{subject_link}]]"
|
return (
|
||||||
|
f"- {fecha_formato} - {self.remitente} - [[cronologia#"
|
||||||
|
f"{self.subject}|{subject_link}]]"
|
||||||
|
)
|
||||||
|
|
||||||
def _estandarizar_remitente(self, remitente):
|
def _estandarizar_remitente(self, remitente):
|
||||||
if "Da:" in remitente:
|
if "Da:" in remitente:
|
||||||
|
@ -103,7 +122,8 @@ class MensajeEmail:
|
||||||
if not nombre and email:
|
if not nombre and email:
|
||||||
nombre = email.split("@")[0]
|
nombre = email.split("@")[0]
|
||||||
elif not nombre and not email:
|
elif not nombre and not email:
|
||||||
nombre_match = re.search(r"([A-Za-z\s]+)\s*<", remitente)
|
patron_nombre = r"([A-Za-z\s]+)\s*<"
|
||||||
|
nombre_match = re.search(patron_nombre, remitente)
|
||||||
if nombre_match:
|
if nombre_match:
|
||||||
nombre = nombre_match.group(1)
|
nombre = nombre_match.group(1)
|
||||||
else:
|
else:
|
||||||
|
@ -117,17 +137,16 @@ class MensajeEmail:
|
||||||
if isinstance(fecha, str):
|
if isinstance(fecha, str):
|
||||||
try:
|
try:
|
||||||
return parsedate_to_datetime(fecha)
|
return parsedate_to_datetime(fecha)
|
||||||
except:
|
except Exception:
|
||||||
return datetime.now()
|
return datetime.now()
|
||||||
return fecha
|
return fecha
|
||||||
|
|
||||||
def _generar_hash(self):
|
def _generar_hash(self):
|
||||||
"""
|
"""
|
||||||
Genera un hash único para el mensaje basado en una combinación de campos
|
Genera un hash único para el mensaje basado en una combinación de
|
||||||
que identifican únicamente el mensaje
|
campos que identifican únicamente el mensaje
|
||||||
"""
|
"""
|
||||||
# Limpiar y normalizar el contenido para el hash
|
# Limpiar y normalizar el contenido para el hash (normaliza espacios)
|
||||||
# Para el hash, sí normalizamos completamente los espacios
|
|
||||||
contenido_hash = re.sub(r"\s+", " ", self.contenido).strip()
|
contenido_hash = re.sub(r"\s+", " ", self.contenido).strip()
|
||||||
|
|
||||||
# Normalizar el subject
|
# Normalizar el subject
|
||||||
|
@ -138,13 +157,11 @@ class MensajeEmail:
|
||||||
# Crear una cadena con los elementos clave del mensaje
|
# Crear una cadena con los elementos clave del mensaje
|
||||||
elementos_hash = [
|
elementos_hash = [
|
||||||
self.remitente.strip(),
|
self.remitente.strip(),
|
||||||
self.fecha.strftime(
|
# Solo hasta minutos para permitir pequeñas variaciones
|
||||||
"%Y%m%d%H%M"
|
self.fecha.strftime("%Y%m%d%H%M"),
|
||||||
), # Solo hasta minutos para permitir pequeñas variaciones
|
|
||||||
subject_normalizado,
|
subject_normalizado,
|
||||||
contenido_hash[
|
# Usar solo los primeros 500 caracteres del contenido normalizado
|
||||||
:500
|
contenido_hash[:500],
|
||||||
], # Usar solo los primeros 500 caracteres del contenido normalizado
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Unir todos los elementos con un separador único
|
# Unir todos los elementos con un separador único
|
||||||
|
@ -152,12 +169,13 @@ class MensajeEmail:
|
||||||
|
|
||||||
# Mostrar información de debug para el hash (solo si está habilitado)
|
# Mostrar información de debug para el hash (solo si está habilitado)
|
||||||
if hasattr(self, "_debug_hash") and self._debug_hash:
|
if hasattr(self, "_debug_hash") and self._debug_hash:
|
||||||
print(f" 🔍 Debug Hash:")
|
print(" 🔍 Debug Hash:")
|
||||||
print(f" - Remitente: '{self.remitente.strip()}'")
|
print(" - Remitente: '" + self.remitente.strip() + "'")
|
||||||
print(f" - Fecha: '{self.fecha.strftime('%Y%m%d%H%M')}'")
|
print(" - Fecha: '" + self.fecha.strftime("%Y%m%d%H%M") + "'")
|
||||||
print(f" - Subject: '{subject_normalizado}'")
|
print(" - Subject: '" + subject_normalizado + "'")
|
||||||
print(f" - Contenido (500 chars): '{contenido_hash[:500]}'")
|
preview = contenido_hash[:500]
|
||||||
print(f" - Texto completo hash: '{texto_hash[:100]}...'")
|
print(" - Contenido (500 chars): '" + preview + "'")
|
||||||
|
print(" - Texto completo hash: '" + texto_hash[:100] + "...'")
|
||||||
|
|
||||||
# Generar el hash
|
# Generar el hash
|
||||||
hash_resultado = hashlib.md5(texto_hash.encode()).hexdigest()
|
hash_resultado = hashlib.md5(texto_hash.encode()).hexdigest()
|
||||||
|
@ -166,7 +184,8 @@ class MensajeEmail:
|
||||||
|
|
||||||
def debug_hash_info(self):
|
def debug_hash_info(self):
|
||||||
"""
|
"""
|
||||||
Muestra información detallada sobre cómo se genera el hash de este mensaje
|
Muestra información detallada de cómo se genera el hash de este
|
||||||
|
mensaje
|
||||||
"""
|
"""
|
||||||
self._debug_hash = True
|
self._debug_hash = True
|
||||||
hash_result = self._generar_hash()
|
hash_result = self._generar_hash()
|
||||||
|
|
|
@ -3,31 +3,73 @@ import os
|
||||||
import hashlib
|
import hashlib
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def _contenido_hash(parte):
|
||||||
|
contenido = parte.get_payload(decode=True) or b""
|
||||||
|
return hashlib.md5(contenido).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def guardar_adjunto(parte, dir_adjuntos):
|
def guardar_adjunto(parte, dir_adjuntos):
|
||||||
nombre = parte.get_filename()
|
nombre = parte.get_filename()
|
||||||
if not nombre:
|
if not nombre:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
nombre = re.sub(r'[<>:"/\\|?*]', '_', nombre)
|
nombre = re.sub(r'[<>:"/\\|?*]', "_", nombre)
|
||||||
ruta = os.path.join(dir_adjuntos, nombre)
|
ruta = os.path.join(dir_adjuntos, nombre)
|
||||||
|
|
||||||
if os.path.exists(ruta):
|
if os.path.exists(ruta):
|
||||||
contenido_nuevo = parte.get_payload(decode=True)
|
hash_nuevo = _contenido_hash(parte)
|
||||||
hash_nuevo = hashlib.md5(contenido_nuevo).hexdigest()
|
with open(ruta, "rb") as f:
|
||||||
|
|
||||||
with open(ruta, 'rb') as f:
|
|
||||||
hash_existente = hashlib.md5(f.read()).hexdigest()
|
hash_existente = hashlib.md5(f.read()).hexdigest()
|
||||||
|
|
||||||
if hash_nuevo == hash_existente:
|
if hash_nuevo == hash_existente:
|
||||||
return ruta
|
return ruta
|
||||||
|
|
||||||
base, ext = os.path.splitext(nombre)
|
base, ext = os.path.splitext(nombre)
|
||||||
i = 1
|
i = 1
|
||||||
while os.path.exists(ruta):
|
while os.path.exists(ruta):
|
||||||
ruta = os.path.join(dir_adjuntos, f"{base}_{i}{ext}")
|
ruta = os.path.join(dir_adjuntos, f"{base}_{i}{ext}")
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
with open(ruta, 'wb') as f:
|
with open(ruta, "wb") as f:
|
||||||
f.write(parte.get_payload(decode=True))
|
f.write(parte.get_payload(decode=True))
|
||||||
|
|
||||||
|
return ruta
|
||||||
|
|
||||||
|
|
||||||
|
def guardar_imagen(parte, dir_adjuntos):
|
||||||
|
"""
|
||||||
|
Guarda una imagen (inline o adjunta). Si no tiene filename, genera uno
|
||||||
|
basado en Content-ID o hash, preservando la extensión según el subtype.
|
||||||
|
Devuelve la ruta completa del archivo guardado.
|
||||||
|
"""
|
||||||
|
nombre = parte.get_filename()
|
||||||
|
if not nombre:
|
||||||
|
# Intentar usar Content-ID
|
||||||
|
content_id = parte.get("Content-ID", "") or parte.get("Content-Id", "")
|
||||||
|
content_id = content_id.strip("<>") if content_id else ""
|
||||||
|
ext = f".{parte.get_content_subtype() or 'bin'}"
|
||||||
|
base = (
|
||||||
|
re.sub(r"[^\w\-]+", "_", content_id)
|
||||||
|
if content_id
|
||||||
|
else _contenido_hash(parte)
|
||||||
|
)
|
||||||
|
nombre = f"img_{base}{ext}"
|
||||||
|
|
||||||
|
nombre = re.sub(r'[<>:"/\\|?*]', "_", nombre)
|
||||||
|
ruta = os.path.join(dir_adjuntos, nombre)
|
||||||
|
|
||||||
|
if os.path.exists(ruta):
|
||||||
|
hash_nuevo = _contenido_hash(parte)
|
||||||
|
with open(ruta, "rb") as f:
|
||||||
|
hash_existente = hashlib.md5(f.read()).hexdigest()
|
||||||
|
if hash_nuevo == hash_existente:
|
||||||
|
return ruta
|
||||||
|
base, ext = os.path.splitext(nombre)
|
||||||
|
i = 1
|
||||||
|
while os.path.exists(ruta):
|
||||||
|
ruta = os.path.join(dir_adjuntos, f"{base}_{i}{ext}")
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
with open(ruta, "wb") as f:
|
||||||
|
f.write(parte.get_payload(decode=True))
|
||||||
|
|
||||||
return ruta
|
return ruta
|
||||||
|
|
|
@ -8,8 +8,7 @@ from pathlib import Path
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from email.utils import parsedate_to_datetime
|
from email.utils import parsedate_to_datetime
|
||||||
from models.mensaje_email import MensajeEmail
|
from models.mensaje_email import MensajeEmail
|
||||||
from utils.attachment_handler import guardar_adjunto
|
from utils.attachment_handler import guardar_adjunto, guardar_imagen
|
||||||
import tempfile
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
@ -74,9 +73,45 @@ def _should_skip_line(line):
|
||||||
return any(line.strip().startswith(header) for header in headers_to_skip)
|
return any(line.strip().startswith(header) for header in headers_to_skip)
|
||||||
|
|
||||||
|
|
||||||
def _html_a_markdown(html):
|
def _find_vault_root(start_path):
|
||||||
"""
|
"""
|
||||||
Convierte contenido HTML a texto markdown, extrayendo el asunto si está presente
|
Busca hacia arriba un directorio que contenga la carpeta hermana '.obsidian'.
|
||||||
|
Devuelve la ruta del directorio raíz del vault o None si no se encuentra.
|
||||||
|
"""
|
||||||
|
current = os.path.abspath(start_path)
|
||||||
|
if os.path.isfile(current):
|
||||||
|
current = os.path.dirname(current)
|
||||||
|
while True:
|
||||||
|
obsidian_dir = os.path.join(current, ".obsidian")
|
||||||
|
if os.path.isdir(obsidian_dir):
|
||||||
|
return current
|
||||||
|
parent = os.path.dirname(current)
|
||||||
|
if parent == current:
|
||||||
|
return None
|
||||||
|
current = parent
|
||||||
|
|
||||||
|
|
||||||
|
def _ruta_relativa_vault(abs_path):
|
||||||
|
"""
|
||||||
|
Convierte una ruta absoluta a una ruta relativa al root del vault Obsidian
|
||||||
|
si se detecta. Si no se detecta, devuelve la ruta original.
|
||||||
|
"""
|
||||||
|
abs_path = os.path.abspath(abs_path)
|
||||||
|
vault_root = _find_vault_root(abs_path)
|
||||||
|
if not vault_root:
|
||||||
|
return abs_path
|
||||||
|
try:
|
||||||
|
rel = os.path.relpath(abs_path, vault_root)
|
||||||
|
# Normalizar separadores a '/'
|
||||||
|
return rel.replace("\\", "/")
|
||||||
|
except Exception:
|
||||||
|
return abs_path
|
||||||
|
|
||||||
|
|
||||||
|
def _html_a_markdown(html, cid_to_link=None):
|
||||||
|
"""
|
||||||
|
Convierte contenido HTML a texto markdown, extrayendo el asunto si está
|
||||||
|
presente
|
||||||
"""
|
"""
|
||||||
if html is None:
|
if html is None:
|
||||||
return (None, "")
|
return (None, "")
|
||||||
|
@ -89,6 +124,17 @@ def _html_a_markdown(html):
|
||||||
|
|
||||||
soup = BeautifulSoup(html, "html.parser")
|
soup = BeautifulSoup(html, "html.parser")
|
||||||
|
|
||||||
|
# Reemplazar imágenes inline referenciadas por cid en su lugar
|
||||||
|
if cid_to_link:
|
||||||
|
for img in soup.find_all("img"):
|
||||||
|
src = img.get("src", "")
|
||||||
|
if src.startswith("cid:"):
|
||||||
|
cid = src[4:].strip("<>")
|
||||||
|
embed_path = cid_to_link.get(cid)
|
||||||
|
if embed_path:
|
||||||
|
# Obsidian embed (single '!') con ruta relativa al vault
|
||||||
|
img.replace_with(soup.new_string(f"![[{embed_path}]]"))
|
||||||
|
|
||||||
# Procesar tablas
|
# Procesar tablas
|
||||||
for table in soup.find_all("table"):
|
for table in soup.find_all("table"):
|
||||||
try:
|
try:
|
||||||
|
@ -126,7 +172,8 @@ def _html_a_markdown(html):
|
||||||
rowspan = int(cell.get("rowspan", 1))
|
rowspan = int(cell.get("rowspan", 1))
|
||||||
colspan = int(cell.get("colspan", 1))
|
colspan = int(cell.get("colspan", 1))
|
||||||
|
|
||||||
# Procesar el texto de la celda reemplazando saltos de línea por <br>
|
# Procesar texto de la celda reemplazando saltos de
|
||||||
|
# línea por <br>
|
||||||
cell_text = cell.get_text().strip()
|
cell_text = cell.get_text().strip()
|
||||||
cell_text = cell_text.replace("\n", "<br>")
|
cell_text = cell_text.replace("\n", "<br>")
|
||||||
cell_text = re.sub(
|
cell_text = re.sub(
|
||||||
|
@ -134,17 +181,16 @@ def _html_a_markdown(html):
|
||||||
) # Eliminar <br> múltiples
|
) # Eliminar <br> múltiples
|
||||||
cell_text = cell_text.strip()
|
cell_text = cell_text.strip()
|
||||||
|
|
||||||
# Rellenar la matriz con el texto y None para las celdas combinadas
|
# Rellenar la matriz con el texto y None para celdas
|
||||||
|
# combinadas
|
||||||
for r in range(rowspan):
|
for r in range(rowspan):
|
||||||
current_row = row_idx + r
|
current_row = row_idx + r
|
||||||
# Expandir matriz si es necesario
|
# Expandir matriz si es necesario
|
||||||
while len(table_matrix) <= current_row:
|
while len(table_matrix) <= current_row:
|
||||||
table_matrix.append([])
|
table_matrix.append([])
|
||||||
# Expandir fila si es necesario
|
# Expandir fila si es necesario
|
||||||
while (
|
while len(table_matrix[current_row]) <= col_idx + colspan - 1:
|
||||||
len(table_matrix[current_row]) <= col_idx + colspan - 1
|
table_matrix[current_row].append(None)
|
||||||
):
|
|
||||||
table_matrix[current_row].append(None)
|
|
||||||
|
|
||||||
for c in range(colspan):
|
for c in range(colspan):
|
||||||
if r == 0 and c == 0:
|
if r == 0 and c == 0:
|
||||||
|
@ -198,9 +244,8 @@ def _html_a_markdown(html):
|
||||||
|
|
||||||
# Reemplazar la tabla HTML con la versión Markdown
|
# Reemplazar la tabla HTML con la versión Markdown
|
||||||
if markdown_table:
|
if markdown_table:
|
||||||
table.replace_with(
|
replacement = "\n" + "\n".join(markdown_table) + "\n"
|
||||||
soup.new_string("\n" + "\n".join(markdown_table) + "\n")
|
table.replace_with(soup.new_string(replacement))
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error procesando tabla: {str(e)}")
|
print(f"Error procesando tabla: {str(e)}")
|
||||||
|
@ -232,7 +277,7 @@ def _html_a_markdown(html):
|
||||||
return (None, html if html else "")
|
return (None, html if html else "")
|
||||||
|
|
||||||
|
|
||||||
def _procesar_email_adjunto(parte, dir_adjuntos):
|
def _procesar_email_adjunto(parte, dir_adjuntos, dir_adjuntos_cronologia=None):
|
||||||
"""
|
"""
|
||||||
Procesa un email que viene como adjunto dentro de otro email.
|
Procesa un email que viene como adjunto dentro de otro email.
|
||||||
"""
|
"""
|
||||||
|
@ -246,17 +291,29 @@ def _procesar_email_adjunto(parte, dir_adjuntos):
|
||||||
payload = subparte.get_payload()
|
payload = subparte.get_payload()
|
||||||
if isinstance(payload, list):
|
if isinstance(payload, list):
|
||||||
for msg in payload:
|
for msg in payload:
|
||||||
mensajes.extend(procesar_eml_interno(msg, dir_adjuntos))
|
mensajes.extend(
|
||||||
|
procesar_eml_interno(
|
||||||
|
msg, dir_adjuntos, dir_adjuntos_cronologia
|
||||||
|
)
|
||||||
|
)
|
||||||
elif isinstance(payload, email.message.Message):
|
elif isinstance(payload, email.message.Message):
|
||||||
mensajes.extend(procesar_eml_interno(payload, dir_adjuntos))
|
mensajes.extend(
|
||||||
|
procesar_eml_interno(
|
||||||
|
payload, dir_adjuntos, dir_adjuntos_cronologia
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Si no es multipart, intentar procesar como mensaje único
|
# Si no es multipart, intentar procesar como mensaje único
|
||||||
payload = parte.get_payload()
|
payload = parte.get_payload()
|
||||||
if isinstance(payload, list):
|
if isinstance(payload, list):
|
||||||
for msg in payload:
|
for msg in payload:
|
||||||
mensajes.extend(procesar_eml_interno(msg, dir_adjuntos))
|
mensajes.extend(
|
||||||
|
procesar_eml_interno(msg, dir_adjuntos, dir_adjuntos_cronologia)
|
||||||
|
)
|
||||||
elif isinstance(payload, email.message.Message):
|
elif isinstance(payload, email.message.Message):
|
||||||
mensajes.extend(procesar_eml_interno(payload, dir_adjuntos))
|
mensajes.extend(
|
||||||
|
procesar_eml_interno(payload, dir_adjuntos, dir_adjuntos_cronologia)
|
||||||
|
)
|
||||||
|
|
||||||
return mensajes
|
return mensajes
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -264,7 +321,7 @@ def _procesar_email_adjunto(parte, dir_adjuntos):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def procesar_eml(ruta_archivo, dir_adjuntos):
|
def procesar_eml(ruta_archivo, dir_adjuntos, dir_adjuntos_cronologia=None):
|
||||||
"""
|
"""
|
||||||
Punto de entrada principal para procesar archivos .eml
|
Punto de entrada principal para procesar archivos .eml
|
||||||
"""
|
"""
|
||||||
|
@ -273,7 +330,7 @@ def procesar_eml(ruta_archivo, dir_adjuntos):
|
||||||
with open(ruta_archivo, "rb") as eml:
|
with open(ruta_archivo, "rb") as eml:
|
||||||
mensaje = BytesParser(policy=policy.default).parse(eml)
|
mensaje = BytesParser(policy=policy.default).parse(eml)
|
||||||
|
|
||||||
mensajes = procesar_eml_interno(mensaje, dir_adjuntos)
|
mensajes = procesar_eml_interno(mensaje, dir_adjuntos, dir_adjuntos_cronologia)
|
||||||
print(f" 📧 Procesamiento completado: {len(mensajes)} mensajes extraídos")
|
print(f" 📧 Procesamiento completado: {len(mensajes)} mensajes extraídos")
|
||||||
return mensajes
|
return mensajes
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -281,7 +338,7 @@ def procesar_eml(ruta_archivo, dir_adjuntos):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def procesar_eml_interno(mensaje, dir_adjuntos):
|
def procesar_eml_interno(mensaje, dir_adjuntos, dir_adjuntos_cronologia=None):
|
||||||
"""
|
"""
|
||||||
Procesa un mensaje de email, ya sea desde archivo o adjunto
|
Procesa un mensaje de email, ya sea desde archivo o adjunto
|
||||||
"""
|
"""
|
||||||
|
@ -296,13 +353,15 @@ def procesar_eml_interno(mensaje, dir_adjuntos):
|
||||||
subject = mensaje.get("subject", "")
|
subject = mensaje.get("subject", "")
|
||||||
if subject:
|
if subject:
|
||||||
# Try to decode if it's encoded
|
# Try to decode if it's encoded
|
||||||
subject = str(email.header.make_header(email.header.decode_header(subject)))
|
decoded = email.header.decode_header(subject)
|
||||||
|
subject = str(email.header.make_header(decoded))
|
||||||
|
|
||||||
contenido = ""
|
contenido = ""
|
||||||
adjuntos = []
|
adjuntos = []
|
||||||
|
imagenes = []
|
||||||
tiene_html = False
|
tiene_html = False
|
||||||
|
|
||||||
# First pass: check for HTML content
|
# Primer pase: detectar si hay HTML
|
||||||
if mensaje.is_multipart():
|
if mensaje.is_multipart():
|
||||||
for parte in mensaje.walk():
|
for parte in mensaje.walk():
|
||||||
if parte.get_content_type() == "text/html":
|
if parte.get_content_type() == "text/html":
|
||||||
|
@ -311,10 +370,29 @@ def procesar_eml_interno(mensaje, dir_adjuntos):
|
||||||
else:
|
else:
|
||||||
tiene_html = mensaje.get_content_type() == "text/html"
|
tiene_html = mensaje.get_content_type() == "text/html"
|
||||||
|
|
||||||
# Second pass: process content and attachments
|
# Segundo pase: procesar contenido y adjuntos
|
||||||
if mensaje.is_multipart():
|
if mensaje.is_multipart():
|
||||||
# Asegurarnos de capturar SOLO una vez el cuerpo principal y no sobrescribirlo
|
# Capturar SOLO una vez el cuerpo principal y no sobrescribirlo
|
||||||
contenido_set = False # flag para no re-asignar contenido principal
|
contenido_set = False # flag para no re-asignar contenido principal
|
||||||
|
# Construir mapa cid->ruta de embed (relativa al vault) para inline
|
||||||
|
cid_to_link = {}
|
||||||
|
if dir_adjuntos_cronologia:
|
||||||
|
for parte in mensaje.walk():
|
||||||
|
ctype = parte.get_content_type()
|
||||||
|
dispo = parte.get_content_disposition()
|
||||||
|
if ctype.startswith("image/") and dispo in (None, "inline"):
|
||||||
|
cid_header = parte.get("Content-ID", "") or parte.get(
|
||||||
|
"Content-Id", ""
|
||||||
|
)
|
||||||
|
if cid_header:
|
||||||
|
cid_clean = cid_header.strip("<>")
|
||||||
|
# Guardar SOLO en adjuntos/cronologia
|
||||||
|
ruta_img = guardar_imagen(parte, dir_adjuntos_cronologia)
|
||||||
|
if ruta_img:
|
||||||
|
# Convertir a ruta relativa al vault
|
||||||
|
embed_path = _ruta_relativa_vault(ruta_img)
|
||||||
|
cid_to_link[cid_clean] = embed_path
|
||||||
|
|
||||||
for parte in mensaje.walk():
|
for parte in mensaje.walk():
|
||||||
content_type = parte.get_content_type()
|
content_type = parte.get_content_type()
|
||||||
|
|
||||||
|
@ -330,7 +408,9 @@ def procesar_eml_interno(mensaje, dir_adjuntos):
|
||||||
if content_type == "text/html":
|
if content_type == "text/html":
|
||||||
html_content = _get_payload_safely(parte)
|
html_content = _get_payload_safely(parte)
|
||||||
if html_content:
|
if html_content:
|
||||||
part_subject, text = _html_a_markdown(html_content)
|
part_subject, text = _html_a_markdown(
|
||||||
|
html_content, cid_to_link
|
||||||
|
)
|
||||||
if not subject and part_subject:
|
if not subject and part_subject:
|
||||||
subject = part_subject
|
subject = part_subject
|
||||||
if text:
|
if text:
|
||||||
|
@ -346,7 +426,9 @@ def procesar_eml_interno(mensaje, dir_adjuntos):
|
||||||
# 2. EMAILS RFC822 ADJUNTOS
|
# 2. EMAILS RFC822 ADJUNTOS
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
elif content_type == "message/rfc822":
|
elif content_type == "message/rfc822":
|
||||||
mensajes_adjuntos = _procesar_email_adjunto(parte, dir_adjuntos)
|
mensajes_adjuntos = _procesar_email_adjunto(
|
||||||
|
parte, dir_adjuntos, dir_adjuntos_cronologia
|
||||||
|
)
|
||||||
mensajes.extend(mensajes_adjuntos)
|
mensajes.extend(mensajes_adjuntos)
|
||||||
|
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
|
@ -356,13 +438,27 @@ def procesar_eml_interno(mensaje, dir_adjuntos):
|
||||||
nombre = parte.get_filename()
|
nombre = parte.get_filename()
|
||||||
if nombre and nombre.lower().endswith(".eml"):
|
if nombre and nombre.lower().endswith(".eml"):
|
||||||
mensajes_adjuntos = _procesar_email_adjunto(
|
mensajes_adjuntos = _procesar_email_adjunto(
|
||||||
parte, dir_adjuntos
|
parte, dir_adjuntos, dir_adjuntos_cronologia
|
||||||
)
|
)
|
||||||
mensajes.extend(mensajes_adjuntos)
|
mensajes.extend(mensajes_adjuntos)
|
||||||
else:
|
else:
|
||||||
ruta_adjunto = guardar_adjunto(parte, dir_adjuntos)
|
# Imagen adjunta (no inline): solo guardar en adjuntos
|
||||||
if ruta_adjunto:
|
if content_type.startswith("image/"):
|
||||||
adjuntos.append(Path(ruta_adjunto).name)
|
ruta_img = guardar_imagen(parte, dir_adjuntos)
|
||||||
|
if ruta_img:
|
||||||
|
adjuntos.append(Path(ruta_img).name)
|
||||||
|
else:
|
||||||
|
ruta_adjunto = guardar_adjunto(parte, dir_adjuntos)
|
||||||
|
if ruta_adjunto:
|
||||||
|
adjuntos.append(Path(ruta_adjunto).name)
|
||||||
|
|
||||||
|
# 4. IMÁGENES INLINE: ya manejadas para embebido; no listar
|
||||||
|
elif content_type.startswith("image/") and (
|
||||||
|
parte.get_content_disposition() in (None, "inline")
|
||||||
|
):
|
||||||
|
# Nada que hacer aquí; ya se guardó en cronologia y
|
||||||
|
# se reemplazó en el HTML
|
||||||
|
pass
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error procesando parte del mensaje: {str(e)}")
|
print(f"Error procesando parte del mensaje: {str(e)}")
|
||||||
|
@ -371,7 +467,8 @@ def procesar_eml_interno(mensaje, dir_adjuntos):
|
||||||
if mensaje.get_content_type() == "text/html":
|
if mensaje.get_content_type() == "text/html":
|
||||||
html_content = _get_payload_safely(mensaje)
|
html_content = _get_payload_safely(mensaje)
|
||||||
if html_content:
|
if html_content:
|
||||||
part_subject, contenido = _html_a_markdown(html_content)
|
# Para mensajes no multipart, no hay inline cid a resolver
|
||||||
|
part_subject, contenido = _html_a_markdown(html_content, {})
|
||||||
if not subject and part_subject:
|
if not subject and part_subject:
|
||||||
subject = part_subject
|
subject = part_subject
|
||||||
else:
|
else:
|
||||||
|
@ -386,7 +483,7 @@ def procesar_eml_interno(mensaje, dir_adjuntos):
|
||||||
subject=subject,
|
subject=subject,
|
||||||
adjuntos=adjuntos,
|
adjuntos=adjuntos,
|
||||||
)
|
)
|
||||||
print(f" ✉️ Mensaje extraído:")
|
print(" ✉️ Mensaje extraído:")
|
||||||
print(f" - Subject: {subject}")
|
print(f" - Subject: {subject}")
|
||||||
print(f" - Remitente: {remitente}")
|
print(f" - Remitente: {remitente}")
|
||||||
print(f" - Fecha: {fecha}")
|
print(f" - Fecha: {fecha}")
|
||||||
|
@ -395,7 +492,7 @@ def procesar_eml_interno(mensaje, dir_adjuntos):
|
||||||
print(f" - Hash generado: {mensaje_nuevo.hash}")
|
print(f" - Hash generado: {mensaje_nuevo.hash}")
|
||||||
mensajes.append(mensaje_nuevo)
|
mensajes.append(mensaje_nuevo)
|
||||||
else:
|
else:
|
||||||
print(f" ⚠️ Mensaje vacío o sin contenido útil - no se agregará")
|
print(" ⚠️ Mensaje vacío o sin contenido útil - no se agregará")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error procesando mensaje: {str(e)}")
|
print(f"Error procesando mensaje: {str(e)}")
|
||||||
|
@ -407,7 +504,7 @@ def _parsear_fecha(fecha_str):
|
||||||
try:
|
try:
|
||||||
fecha = parsedate_to_datetime(fecha_str)
|
fecha = parsedate_to_datetime(fecha_str)
|
||||||
return fecha.replace(tzinfo=None) # Remove timezone info
|
return fecha.replace(tzinfo=None) # Remove timezone info
|
||||||
except:
|
except Exception:
|
||||||
try:
|
try:
|
||||||
fecha_match = re.search(
|
fecha_match = re.search(
|
||||||
r"venerd=EC (\d{1,2}) (\w+) (\d{4}) (\d{1,2}):(\d{2})", fecha_str
|
r"venerd=EC (\d{1,2}) (\w+) (\d{4}) (\d{1,2}):(\d{2})", fecha_str
|
||||||
|
@ -430,6 +527,6 @@ def _parsear_fecha(fecha_str):
|
||||||
}
|
}
|
||||||
mes_num = meses_it.get(mes.lower(), 1)
|
mes_num = meses_it.get(mes.lower(), 1)
|
||||||
return datetime(int(año), mes_num, int(dia), int(hora), int(minuto))
|
return datetime(int(año), mes_num, int(dia), int(hora), int(minuto))
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return datetime.now()
|
return datetime.now()
|
||||||
|
|
|
@ -62,6 +62,7 @@ def main():
|
||||||
# Construir rutas de salida en working_directory
|
# Construir rutas de salida en working_directory
|
||||||
output_file = os.path.join(working_directory, cronologia_file)
|
output_file = os.path.join(working_directory, cronologia_file)
|
||||||
attachments_path = os.path.join(working_directory, attachments_dir)
|
attachments_path = os.path.join(working_directory, attachments_dir)
|
||||||
|
attachments_crono_path = os.path.join(attachments_path, "cronologia")
|
||||||
|
|
||||||
# Debug prints
|
# Debug prints
|
||||||
print(f"Working/Output directory: {working_directory}")
|
print(f"Working/Output directory: {working_directory}")
|
||||||
|
@ -80,6 +81,7 @@ def main():
|
||||||
# Asegurar directorios de salida
|
# Asegurar directorios de salida
|
||||||
os.makedirs(working_directory, exist_ok=True)
|
os.makedirs(working_directory, exist_ok=True)
|
||||||
os.makedirs(attachments_path, exist_ok=True)
|
os.makedirs(attachments_path, exist_ok=True)
|
||||||
|
os.makedirs(attachments_crono_path, exist_ok=True)
|
||||||
|
|
||||||
# Check if input directory exists and has files
|
# Check if input directory exists and has files
|
||||||
input_path = Path(input_dir)
|
input_path = Path(input_dir)
|
||||||
|
@ -110,7 +112,9 @@ def main():
|
||||||
print(f"\n{'='*60}")
|
print(f"\n{'='*60}")
|
||||||
print(f"Processing file: {archivo}")
|
print(f"Processing file: {archivo}")
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
nuevos_mensajes = procesar_eml(archivo, attachments_path)
|
nuevos_mensajes = procesar_eml(
|
||||||
|
archivo, attachments_path, attachments_crono_path
|
||||||
|
)
|
||||||
print(f"Extracted {len(nuevos_mensajes)} messages from {archivo.name}")
|
print(f"Extracted {len(nuevos_mensajes)} messages from {archivo.name}")
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
total_procesados += len(nuevos_mensajes)
|
total_procesados += len(nuevos_mensajes)
|
||||||
|
|
|
@ -135,6 +135,21 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 2px solid #e2e8f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 1em;
|
||||||
|
background: white;
|
||||||
|
transition: border-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #667eea;
|
||||||
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
input[type="text"] {
|
input[type="text"] {
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
border: 2px solid #e2e8f0;
|
border: 2px solid #e2e8f0;
|
||||||
|
@ -461,6 +476,18 @@
|
||||||
<button class="btn btn-secondary" onclick="refreshModels()">
|
<button class="btn btn-secondary" onclick="refreshModels()">
|
||||||
🔄 Actualizar
|
🔄 Actualizar
|
||||||
</button>
|
</button>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="sort-select"
|
||||||
|
style="margin-left:10px; margin-right:6px; color:#4a5568; font-weight:600;">Ordenar
|
||||||
|
por:</label>
|
||||||
|
<select id="sort-select">
|
||||||
|
<option value="modified_desc">Última modificación (recientes primero)</option>
|
||||||
|
<option value="modified_asc">Última modificación (antiguos primero)</option>
|
||||||
|
<option value="size_desc">Tamaño (grandes primero)</option>
|
||||||
|
<option value="size_asc">Tamaño (pequeños primero)</option>
|
||||||
|
<option value="name_asc">Nombre (A-Z)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -497,6 +524,7 @@
|
||||||
// Estado global de la aplicación
|
// Estado global de la aplicación
|
||||||
let currentModels = [];
|
let currentModels = [];
|
||||||
let isLoading = false;
|
let isLoading = false;
|
||||||
|
let currentSort = 'modified_desc';
|
||||||
|
|
||||||
// Inicializar aplicación
|
// Inicializar aplicación
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
@ -509,6 +537,16 @@
|
||||||
downloadModel();
|
downloadModel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Configurar selector de orden
|
||||||
|
const sortSelect = document.getElementById('sort-select');
|
||||||
|
if (sortSelect) {
|
||||||
|
sortSelect.value = currentSort;
|
||||||
|
sortSelect.addEventListener('change', function () {
|
||||||
|
currentSort = this.value;
|
||||||
|
applySortAndRender();
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Verificar estado de Ollama
|
// Verificar estado de Ollama
|
||||||
|
@ -557,7 +595,7 @@
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
currentModels = data.models || [];
|
currentModels = data.models || [];
|
||||||
updateStats(data);
|
updateStats(data);
|
||||||
renderModels(currentModels);
|
applySortAndRender();
|
||||||
} else {
|
} else {
|
||||||
showError(`Error al cargar modelos: ${data.message}`);
|
showError(`Error al cargar modelos: ${data.message}`);
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
|
@ -578,6 +616,34 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Aplicar orden y renderizar
|
||||||
|
function applySortAndRender() {
|
||||||
|
const sorted = [...currentModels];
|
||||||
|
const getSize = (m) => typeof m.size === 'number' ? m.size : (parseInt(m.size, 10) || 0);
|
||||||
|
const getModifiedTs = (m) => m.modified_at ? (Date.parse(m.modified_at) || 0) : 0;
|
||||||
|
|
||||||
|
switch (currentSort) {
|
||||||
|
case 'size_desc':
|
||||||
|
sorted.sort((a, b) => getSize(b) - getSize(a));
|
||||||
|
break;
|
||||||
|
case 'size_asc':
|
||||||
|
sorted.sort((a, b) => getSize(a) - getSize(b));
|
||||||
|
break;
|
||||||
|
case 'modified_asc':
|
||||||
|
sorted.sort((a, b) => getModifiedTs(a) - getModifiedTs(b));
|
||||||
|
break;
|
||||||
|
case 'name_asc':
|
||||||
|
sorted.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
|
||||||
|
break;
|
||||||
|
case 'modified_desc':
|
||||||
|
default:
|
||||||
|
sorted.sort((a, b) => getModifiedTs(b) - getModifiedTs(a));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderModels(sorted);
|
||||||
|
}
|
||||||
|
|
||||||
// Actualizar estadísticas
|
// Actualizar estadísticas
|
||||||
function updateStats(data) {
|
function updateStats(data) {
|
||||||
const statsElement = document.getElementById('stats');
|
const statsElement = document.getElementById('stats');
|
||||||
|
|
|
@ -1,5 +1,31 @@
|
||||||
{
|
{
|
||||||
"history": [
|
"history": [
|
||||||
|
{
|
||||||
|
"id": "286de385",
|
||||||
|
"group_id": "2",
|
||||||
|
"script_name": "main.py",
|
||||||
|
"executed_date": "2025-08-08T13:31:57.810744Z",
|
||||||
|
"arguments": [],
|
||||||
|
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
|
||||||
|
"python_env": "tia_scripting",
|
||||||
|
"executable_type": "pythonw.exe",
|
||||||
|
"status": "running",
|
||||||
|
"pid": 63664,
|
||||||
|
"execution_time": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "19b3a39f",
|
||||||
|
"group_id": "2",
|
||||||
|
"script_name": "main.py",
|
||||||
|
"executed_date": "2025-08-08T12:08:20.490182Z",
|
||||||
|
"arguments": [],
|
||||||
|
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
|
||||||
|
"python_env": "tia_scripting",
|
||||||
|
"executable_type": "pythonw.exe",
|
||||||
|
"status": "running",
|
||||||
|
"pid": 66300,
|
||||||
|
"execution_time": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "23248897",
|
"id": "23248897",
|
||||||
"group_id": "2",
|
"group_id": "2",
|
||||||
|
|
214
data/log.txt
214
data/log.txt
|
@ -1,107 +1,107 @@
|
||||||
[11:43:49] Iniciando ejecución de x1.py en C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\17 - E5.006880 - Modifica O&U - RSC098...
|
[14:55:47] Iniciando ejecución de x1.py en C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\17 - E5.006880 - Modifica O&U - RSC098...
|
||||||
[11:43:49] ✅ Configuración cargada exitosamente
|
[14:55:47] ✅ Configuración cargada exitosamente
|
||||||
[11:43:49] Working/Output directory: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\17 - E5.006880 - Modifica O&U - RSC098
|
[14:55:47] Working/Output directory: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\17 - E5.006880 - Modifica O&U - RSC098
|
||||||
[11:43:49] Input directory: C:\Trabajo\SIDEL\17 - E5.006880 - Modifica O&U - RSC098\Reporte\Emails
|
[14:55:47] Input directory: C:\Trabajo\SIDEL\17 - E5.006880 - Modifica O&U - RSC098\Reporte\Emails
|
||||||
[11:43:49] Output file: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\17 - E5.006880 - Modifica O&U - RSC098\cronologia.md
|
[14:55:47] Output file: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\17 - E5.006880 - Modifica O&U - RSC098\cronologia.md
|
||||||
[11:43:49] Attachments directory: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\17 - E5.006880 - Modifica O&U - RSC098\adjuntos
|
[14:55:47] Attachments directory: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\17 - E5.006880 - Modifica O&U - RSC098\adjuntos
|
||||||
[11:43:49] Beautify rules file: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\EmailCrono\config\beautify_rules.json
|
[14:55:47] Beautify rules file: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\EmailCrono\config\beautify_rules.json
|
||||||
[11:43:49] Found 1 .eml files
|
[14:55:47] Found 1 .eml files
|
||||||
[11:43:49] Creando cronología nueva (archivo se sobrescribirá)
|
[14:55:47] Creando cronología nueva (archivo se sobrescribirá)
|
||||||
[11:43:49] ============================================================
|
[14:55:47] ============================================================
|
||||||
[11:43:49] Processing file: C:\Trabajo\SIDEL\17 - E5.006880 - Modifica O&U - RSC098\Reporte\Emails\R_ E5.006880 - RSC098 - Nigerian Breweries_ URGENT.eml
|
[14:55:47] Processing file: C:\Trabajo\SIDEL\17 - E5.006880 - Modifica O&U - RSC098\Reporte\Emails\R_ E5.006880 - RSC098 - Nigerian Breweries_ URGENT.eml
|
||||||
[11:43:49] 📧 Abriendo archivo: C:\Trabajo\SIDEL\17 - E5.006880 - Modifica O&U - RSC098\Reporte\Emails\R_ E5.006880 - RSC098 - Nigerian Breweries_ URGENT.eml
|
[14:55:47] 📧 Abriendo archivo: C:\Trabajo\SIDEL\17 - E5.006880 - Modifica O&U - RSC098\Reporte\Emails\R_ E5.006880 - RSC098 - Nigerian Breweries_ URGENT.eml
|
||||||
[11:43:49] ✉️ Mensaje extraído:
|
[14:55:48] ✉️ Mensaje extraído:
|
||||||
[11:43:49] - Subject: R: {EXT} R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
[14:55:48] - Subject: R: {EXT} R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
||||||
[11:43:49] - Remitente: "Passera, Alessandro" <Alessandro.Passera@sidel.com>
|
[14:55:48] - Remitente: "Passera, Alessandro" <Alessandro.Passera@sidel.com>
|
||||||
[11:43:49] - Fecha: 2025-08-08 07:49:28
|
[14:55:48] - Fecha: 2025-08-08 07:49:28
|
||||||
[11:43:49] - Adjuntos: 0 archivos
|
[14:55:48] - Adjuntos: 0 archivos
|
||||||
[11:43:49] - Contenido: 4735 caracteres
|
[14:55:48] - Contenido: 5160 caracteres
|
||||||
[11:43:49] - Hash generado: 48f94bf24945f73bc08c1c0cf8c1e8bb
|
[14:55:48] - Hash generado: 48f94bf24945f73bc08c1c0cf8c1e8bb
|
||||||
[11:43:49] ✉️ Mensaje extraído:
|
[14:55:48] ✉️ Mensaje extraído:
|
||||||
[11:43:49] - Subject: RE: {EXT} R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
[14:55:48] - Subject: RE: {EXT} R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
||||||
[11:43:49] - Remitente: "Bii, Vickodyne" <vickodyne.bii@sidel.com>
|
[14:55:48] - Remitente: "Bii, Vickodyne" <vickodyne.bii@sidel.com>
|
||||||
[11:43:49] - Fecha: 2025-08-08 05:46:30
|
[14:55:48] - Fecha: 2025-08-08 05:46:30
|
||||||
[11:43:49] - Adjuntos: 0 archivos
|
[14:55:48] - Adjuntos: 0 archivos
|
||||||
[11:43:49] - Contenido: 4259 caracteres
|
[14:55:48] - Contenido: 4686 caracteres
|
||||||
[11:43:49] - Hash generado: 82709d4677b90d79bb02e13cfe86924e
|
[14:55:48] - Hash generado: 352a3d37ac274b11822ca5527cd8865b
|
||||||
[11:43:49] ✉️ Mensaje extraído:
|
[14:55:48] ✉️ Mensaje extraído:
|
||||||
[11:43:49] - Subject: R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
[14:55:48] - Subject: R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
||||||
[11:43:49] - Remitente: "walter.orsi@teknors.com" <walter.orsi@teknors.com>
|
[14:55:48] - Remitente: "walter.orsi@teknors.com" <walter.orsi@teknors.com>
|
||||||
[11:43:49] - Fecha: 2025-08-07 15:55:58
|
[14:55:48] - Fecha: 2025-08-07 15:55:58
|
||||||
[11:43:49] - Adjuntos: 0 archivos
|
[14:55:48] - Adjuntos: 0 archivos
|
||||||
[11:43:49] - Contenido: 3235 caracteres
|
[14:55:48] - Contenido: 3583 caracteres
|
||||||
[11:43:49] - Hash generado: cceec9818de1a4491214af4b6d96e143
|
[14:55:48] - Hash generado: b2558be8631ba7d14210a4a3379dfdad
|
||||||
[11:43:49] ✉️ Mensaje extraído:
|
[14:55:48] ✉️ Mensaje extraído:
|
||||||
[11:43:49] - Subject: R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
[14:55:48] - Subject: R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
||||||
[11:43:49] - Remitente: "Passera, Alessandro" <Alessandro.Passera@sidel.com>
|
[14:55:48] - Remitente: "Passera, Alessandro" <Alessandro.Passera@sidel.com>
|
||||||
[11:43:49] - Fecha: 2025-08-07 13:07:11
|
[14:55:48] - Fecha: 2025-08-07 13:07:11
|
||||||
[11:43:49] - Adjuntos: 0 archivos
|
[14:55:48] - Adjuntos: 0 archivos
|
||||||
[11:43:49] - Contenido: 2398 caracteres
|
[14:55:48] - Contenido: 2485 caracteres
|
||||||
[11:43:49] - Hash generado: dc05b2959920f679cd60e8a29685badc
|
[14:55:48] - Hash generado: dc05b2959920f679cd60e8a29685badc
|
||||||
[11:43:49] ✉️ Mensaje extraído:
|
[14:55:48] ✉️ Mensaje extraído:
|
||||||
[11:43:49] - Subject: R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
[14:55:48] - Subject: R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
||||||
[11:43:49] - Remitente: "Passera, Alessandro" <Alessandro.Passera@sidel.com>
|
[14:55:48] - Remitente: "Passera, Alessandro" <Alessandro.Passera@sidel.com>
|
||||||
[11:43:49] - Fecha: 2025-08-07 12:59:15
|
[14:55:48] - Fecha: 2025-08-07 12:59:15
|
||||||
[11:43:49] - Adjuntos: 0 archivos
|
[14:55:48] - Adjuntos: 0 archivos
|
||||||
[11:43:49] - Contenido: 1613 caracteres
|
[14:55:48] - Contenido: 1700 caracteres
|
||||||
[11:43:49] - Hash generado: a848be3351ae2cc44bafb0f322a78690
|
[14:55:48] - Hash generado: a848be3351ae2cc44bafb0f322a78690
|
||||||
[11:43:49] ✉️ Mensaje extraído:
|
[14:55:48] ✉️ Mensaje extraído:
|
||||||
[11:43:49] - Subject: RE: {EXT} R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
[14:55:48] - Subject: RE: {EXT} R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
||||||
[11:43:49] - Remitente: Miguel Angel Vera <miguelverateknors@gmail.com>
|
[14:55:48] - Remitente: Miguel Angel Vera <miguelverateknors@gmail.com>
|
||||||
[11:43:49] - Fecha: 2025-08-08 09:41:58
|
[14:55:48] - Fecha: 2025-08-08 09:41:58
|
||||||
[11:43:49] - Adjuntos: 0 archivos
|
[14:55:48] - Adjuntos: 0 archivos
|
||||||
[11:43:49] - Contenido: 4735 caracteres
|
[14:55:48] - Contenido: 5160 caracteres
|
||||||
[11:43:49] - Hash generado: 430cc918020c3c8db795995baa26cb78
|
[14:55:48] - Hash generado: 430cc918020c3c8db795995baa26cb78
|
||||||
[11:43:49] 📧 Procesamiento completado: 6 mensajes extraídos
|
[14:55:48] 📧 Procesamiento completado: 6 mensajes extraídos
|
||||||
[11:43:49] Extracted 6 messages from R_ E5.006880 - RSC098 - Nigerian Breweries_ URGENT.eml
|
[14:55:48] Extracted 6 messages from R_ E5.006880 - RSC098 - Nigerian Breweries_ URGENT.eml
|
||||||
[11:43:49] --- Msg 1/6 from R_ E5.006880 - RSC098 - Nigerian Breweries_ URGENT.eml ---
|
[14:55:48] --- Msg 1/6 from R_ E5.006880 - RSC098 - Nigerian Breweries_ URGENT.eml ---
|
||||||
[11:43:49] Remitente: Passera, Alessandro
|
[14:55:48] Remitente: Passera, Alessandro
|
||||||
[11:43:49] Fecha: 2025-08-08 07:49:28
|
[14:55:48] Fecha: 2025-08-08 07:49:28
|
||||||
[11:43:49] Subject: R: {EXT} R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
[14:55:48] Subject: R: {EXT} R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
||||||
[11:43:49] Hash: 48f94bf24945f73bc08c1c0cf8c1e8bb
|
[14:55:48] Hash: 48f94bf24945f73bc08c1c0cf8c1e8bb
|
||||||
[11:43:49] Adjuntos: []
|
[14:55:48] Adjuntos: []
|
||||||
[11:43:49] ✓ NUEVO mensaje - Agregando a la cronología
|
[14:55:48] ✓ NUEVO mensaje - Agregando a la cronología
|
||||||
[11:43:49] --- Msg 2/6 from R_ E5.006880 - RSC098 - Nigerian Breweries_ URGENT.eml ---
|
[14:55:48] --- Msg 2/6 from R_ E5.006880 - RSC098 - Nigerian Breweries_ URGENT.eml ---
|
||||||
[11:43:49] Remitente: Bii, Vickodyne
|
[14:55:48] Remitente: Bii, Vickodyne
|
||||||
[11:43:49] Fecha: 2025-08-08 05:46:30
|
[14:55:48] Fecha: 2025-08-08 05:46:30
|
||||||
[11:43:49] Subject: RE: {EXT} R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
[14:55:48] Subject: RE: {EXT} R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
||||||
[11:43:49] Hash: 82709d4677b90d79bb02e13cfe86924e
|
[14:55:48] Hash: 352a3d37ac274b11822ca5527cd8865b
|
||||||
[11:43:49] Adjuntos: []
|
[14:55:48] Adjuntos: []
|
||||||
[11:43:49] ✓ NUEVO mensaje - Agregando a la cronología
|
[14:55:48] ✓ NUEVO mensaje - Agregando a la cronología
|
||||||
[11:43:49] --- Msg 3/6 from R_ E5.006880 - RSC098 - Nigerian Breweries_ URGENT.eml ---
|
[14:55:48] --- Msg 3/6 from R_ E5.006880 - RSC098 - Nigerian Breweries_ URGENT.eml ---
|
||||||
[11:43:49] Remitente: walter.orsi@teknors.com
|
[14:55:48] Remitente: walter.orsi@teknors.com
|
||||||
[11:43:49] Fecha: 2025-08-07 15:55:58
|
[14:55:48] Fecha: 2025-08-07 15:55:58
|
||||||
[11:43:49] Subject: R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
[14:55:48] Subject: R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
||||||
[11:43:49] Hash: cceec9818de1a4491214af4b6d96e143
|
[14:55:48] Hash: b2558be8631ba7d14210a4a3379dfdad
|
||||||
[11:43:49] Adjuntos: []
|
[14:55:48] Adjuntos: []
|
||||||
[11:43:49] ✓ NUEVO mensaje - Agregando a la cronología
|
[14:55:48] ✓ NUEVO mensaje - Agregando a la cronología
|
||||||
[11:43:49] --- Msg 4/6 from R_ E5.006880 - RSC098 - Nigerian Breweries_ URGENT.eml ---
|
[14:55:48] --- Msg 4/6 from R_ E5.006880 - RSC098 - Nigerian Breweries_ URGENT.eml ---
|
||||||
[11:43:49] Remitente: Passera, Alessandro
|
[14:55:48] Remitente: Passera, Alessandro
|
||||||
[11:43:49] Fecha: 2025-08-07 13:07:11
|
[14:55:48] Fecha: 2025-08-07 13:07:11
|
||||||
[11:43:49] Subject: R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
[14:55:48] Subject: R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
||||||
[11:43:49] Hash: dc05b2959920f679cd60e8a29685badc
|
[14:55:48] Hash: dc05b2959920f679cd60e8a29685badc
|
||||||
[11:43:49] Adjuntos: []
|
[14:55:48] Adjuntos: []
|
||||||
[11:43:49] ✓ NUEVO mensaje - Agregando a la cronología
|
[14:55:48] ✓ NUEVO mensaje - Agregando a la cronología
|
||||||
[11:43:49] --- Msg 5/6 from R_ E5.006880 - RSC098 - Nigerian Breweries_ URGENT.eml ---
|
[14:55:48] --- Msg 5/6 from R_ E5.006880 - RSC098 - Nigerian Breweries_ URGENT.eml ---
|
||||||
[11:43:49] Remitente: Passera, Alessandro
|
[14:55:48] Remitente: Passera, Alessandro
|
||||||
[11:43:49] Fecha: 2025-08-07 12:59:15
|
[14:55:48] Fecha: 2025-08-07 12:59:15
|
||||||
[11:43:49] Subject: R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
[14:55:48] Subject: R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
||||||
[11:43:49] Hash: a848be3351ae2cc44bafb0f322a78690
|
[14:55:48] Hash: a848be3351ae2cc44bafb0f322a78690
|
||||||
[11:43:49] Adjuntos: []
|
[14:55:48] Adjuntos: []
|
||||||
[11:43:49] ✓ NUEVO mensaje - Agregando a la cronología
|
[14:55:48] ✓ NUEVO mensaje - Agregando a la cronología
|
||||||
[11:43:49] --- Msg 6/6 from R_ E5.006880 - RSC098 - Nigerian Breweries_ URGENT.eml ---
|
[14:55:48] --- Msg 6/6 from R_ E5.006880 - RSC098 - Nigerian Breweries_ URGENT.eml ---
|
||||||
[11:43:49] Remitente: Miguel Angel Vera
|
[14:55:48] Remitente: Miguel Angel Vera
|
||||||
[11:43:49] Fecha: 2025-08-08 09:41:58
|
[14:55:48] Fecha: 2025-08-08 09:41:58
|
||||||
[11:43:49] Subject: RE: {EXT} R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
[14:55:48] Subject: RE: {EXT} R: E5.006880 - RSC098 - Nigerian Breweries: URGENT
|
||||||
[11:43:49] Hash: 430cc918020c3c8db795995baa26cb78
|
[14:55:48] Hash: 430cc918020c3c8db795995baa26cb78
|
||||||
[11:43:49] Adjuntos: []
|
[14:55:48] Adjuntos: []
|
||||||
[11:43:49] ✓ NUEVO mensaje - Agregando a la cronología
|
[14:55:48] ✓ NUEVO mensaje - Agregando a la cronología
|
||||||
[11:43:49] Estadísticas de procesamiento:
|
[14:55:48] Estadísticas de procesamiento:
|
||||||
[11:43:49] - Total mensajes encontrados: 6
|
[14:55:48] - Total mensajes encontrados: 6
|
||||||
[11:43:49] - Mensajes únicos añadidos: 6
|
[14:55:48] - Mensajes únicos añadidos: 6
|
||||||
[11:43:49] - Mensajes duplicados ignorados: 0
|
[14:55:48] - Mensajes duplicados ignorados: 0
|
||||||
[11:43:49] Writing 6 messages to C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\17 - E5.006880 - Modifica O&U - RSC098\cronologia.md
|
[14:55:48] Writing 6 messages to C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\17 - E5.006880 - Modifica O&U - RSC098\cronologia.md
|
||||||
[11:43:49] ✅ Cronología guardada exitosamente en: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\17 - E5.006880 - Modifica O&U - RSC098\cronologia.md
|
[14:55:48] ✅ Cronología guardada exitosamente en: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\17 - E5.006880 - Modifica O&U - RSC098\cronologia.md
|
||||||
[11:43:49] 📊 Total de mensajes en la cronología: 6
|
[14:55:48] 📊 Total de mensajes en la cronología: 6
|
||||||
[11:43:49] Ejecución de x1.py finalizada (success). Duración: 0:00:00.353973.
|
[14:55:48] Ejecución de x1.py finalizada (success). Duración: 0:00:00.497219.
|
||||||
[11:43:49] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\EmailCrono\.log\log_x1.txt
|
[14:55:48] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\EmailCrono\.log\log_x1.txt
|
||||||
|
|
Loading…
Reference in New Issue