2.2.0: la fecha de verdad manda, sin preguntar nada
- R.fecha_real: la fecha sale de lo más antiguo que sabe el disco. Al copiar en Windows la creación pasa a ser la de la copia y la modificación se conserva; al renderizar, las dos son nuevas. - R.base_derivado y _heredar_fechas: un render hereda la fecha del original, en la misma carpeta o en otra si el nombre es propio y único. Si hay dos candidatos iguales no hereda. - El plan archiva el material en el mes en que se grabó, no en el que dice la carpeta, y explica lo que hizo en vez de pedir una decisión. Los 727 GB de las dos carpetas que mentían se resuelven solos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Este commit está contenido en:
16
README.md
16
README.md
@@ -393,6 +393,22 @@ acciones trae `automatico` (las que se pueden hacer solas) y el progreso del mot
|
|||||||
ordenar dejan de contar como pendientes mientras no haya un plan calculado: después de ordenarlo todo la
|
ordenar dejan de contar como pendientes mientras no haya un plan calculado: después de ordenarlo todo la
|
||||||
lista seguía diciendo que faltaban dos cosas que en realidad ya no tenían nada.
|
lista seguía diciendo que faltaban dos cosas que en realidad ya no tenían nada.
|
||||||
|
|
||||||
|
2.2.0, la fecha de verdad manda y ya no hay que decidir nada a mano:
|
||||||
|
- **La fecha sale de lo más antiguo que sabe el disco** (`R.fecha_real`): al copiar en Windows la fecha de
|
||||||
|
creación pasa a ser la de la copia y la de modificación se conserva; al renderizar, las dos son nuevas.
|
||||||
|
Quedarse con la más antigua de las dos da siempre la mejor estimación de cuándo se grabó.
|
||||||
|
- **Un render hereda la fecha del original** (`R.base_derivado`, `_heredar_fechas`). «C0001_10bit.mp4» se
|
||||||
|
archiva en el mes en que se grabó «C0001.MP4», no en el mes en que se convirtió. Reconoce los sufijos
|
||||||
|
habituales (`_10bit`, `_ligero`, `_h264`, `_proxy`, `_LT`, `_HQ`, `_render`, `_export`, `_master`, `_final`)
|
||||||
|
y busca el original en la misma carpeta o, si el nombre es bastante propio y único, en toda la carpeta del
|
||||||
|
cliente. Si hay dos candidatos con el mismo nombre no hereda nada. Las conversiones que hace la propia app
|
||||||
|
ya conservaban la fecha del original, tanto por ffmpeg como por Resolve.
|
||||||
|
- **Cuando la carpeta y los archivos no coinciden, manda el archivo.** El nombre del clip lo pone la cámara y
|
||||||
|
la fecha la pone el disco; el nombre de la carpeta lo escribió una persona con prisa. Así el material se
|
||||||
|
archiva en el mes en que se grabó sin preguntarle nada a nadie, y el aviso ya no pide una decisión: dice lo
|
||||||
|
que la app hizo y por qué. En el material real, los 727 GB de las dos carpetas que mentían se resuelven
|
||||||
|
solos: lo de «02-marzo» va a diciembre de 2025 y lo de «08-Agosto» a abril de 2026.
|
||||||
|
|
||||||
Probado el 9 sep 2026 sobre una copia de ensayo: plan → aplicar → deshacer → volver a aplicar deja el árbol
|
Probado el 9 sep 2026 sobre una copia de ensayo: plan → aplicar → deshacer → volver a aplicar deja el árbol
|
||||||
exacto; conversión ffmpeg verificada y reemplazo con original a `_Para borrar`. El puente de Resolve conectó
|
exacto; conversión ffmpeg verificada y reemplazo con original a `_Para borrar`. El puente de Resolve conectó
|
||||||
con Resolve Studio 21.1 pero no pudo abrir proyecto porque Resolve estaba en el Gestor de proyectos:
|
con Resolve Studio 21.1 pero no pudo abrir proyecto porque Resolve estaba en el Gestor de proyectos:
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"nombre": "OrganiLAB",
|
"nombre": "OrganiLAB",
|
||||||
"version": "2.1.0",
|
"version": "2.2.0",
|
||||||
"builtAt": "2026-09-17T02:51:40.000Z",
|
"builtAt": "2026-09-17T02:58:48.000Z",
|
||||||
"archivo": "OrganizadorLabstream-portatil-2.1.0.zip",
|
"archivo": "OrganizadorLabstream-portatil-2.2.0.zip",
|
||||||
"size": 198698005,
|
"size": 198699833,
|
||||||
"sha256": "1ba2cd2dde181b42c6de5cc0860fa52d6d201c874bb1b28285200f984c240bf4",
|
"sha256": "89d8437d47c05d9fcccb456b8b96fc3a2e4efddf4f48c651e992f91aba7f7092",
|
||||||
"app": {
|
"app": {
|
||||||
"archivo": "OrganizadorLabstream-app-2.1.0.zip",
|
"archivo": "OrganizadorLabstream-app-2.2.0.zip",
|
||||||
"size": 14878256,
|
"size": 14880084,
|
||||||
"sha256": "381cbd045a79c4ed920d4101aabb8b234034f93b99b816732bc8b5c2dd7d73b1",
|
"sha256": "e1baad7197559579c3e202f15a2f857b973c696acbb078be19302e7e3e5ed84f",
|
||||||
"url": "/api/herramientas/OrganizadorLabstream-app-2.1.0.zip"
|
"url": "/api/herramientas/OrganizadorLabstream-app-2.2.0.zip"
|
||||||
},
|
},
|
||||||
"notes": "",
|
"notes": "",
|
||||||
"firma": {
|
"firma": {
|
||||||
"alg": "rsa2048-pkcs1v15-sha256",
|
"alg": "rsa2048-pkcs1v15-sha256",
|
||||||
"valor": "944a227ccc3edf2bc3038ca2d6ab507efac49c5b3384df1a6659073e9a88e84e853b73617e47f03ad53bbac04a24bb37082d38bdc918b6c22bd8c93158d75ec34190abe1fa082b8eb0bf3efb80a42dd80bbac6e1b28d839675654f674e82ab8023eb10b76d1df198cb14bcd847edee12b74f89851160937768981be8e327ae151b94bf1c38339d1e0210a653d593f54d26a3c6140f0401dcfe0b039fbf20b188a14f263f19242824f1c351f3837c5bd07d486a0c5e18376f3c30e2e1e61013e18b66883b659a326f5c38a875527372013ec111c579ddd50ee7d3c4aa4fe757971b3b022cc9ea004472db397d5ab189a29c5450ba8b7b43a8c5f767b0b7d3087e"
|
"valor": "663c5b40876996a24ce05dfe9df6a9f71f8a7391b3f2a6ad15865b95038f12eb42836d0bdc42c14437568a387985f95dafe34f3c1e30700fe1f40ca851e56a7c78cf834bbcb21e3da244eecdd0805d8e5dcec754a4e7cfb8a10281e7eea7fde9dff788e3eac74fe57cb94a664a48008b77eb016439b8e1fa459b2d72fcbc168f96569dc0e41610e70b24b45bc2a1c7f282f9f30d76062ac045f623c7b82a415296aef45566de5628ccc3b9b3f7f537d854953a059279b3522d46285c1ed6c52fbf55de26fe2cabe774527f1de67eabf15096b6693f5454fd35835d06801d7837cf3022309ec3a5f0ec5eff77739d06be1e8cff271c715ae43142627b1e7482e4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,7 +34,7 @@ import actualizador
|
|||||||
asistente.O = sys.modules[__name__]
|
asistente.O = sys.modules[__name__]
|
||||||
actualizador.O = sys.modules[__name__]
|
actualizador.O = sys.modules[__name__]
|
||||||
|
|
||||||
VERSION = "2.1.0"
|
VERSION = "2.2.0"
|
||||||
|
|
||||||
CONGELADO = getattr(sys, "frozen", False)
|
CONGELADO = getattr(sys, "frozen", False)
|
||||||
SIN_VENTANA = getattr(subprocess, "CREATE_NO_WINDOW", 0) # ffmpeg/ffprobe/net sin abrir consolas negras # empaquetado como .exe con PyInstaller
|
SIN_VENTANA = getattr(subprocess, "CREATE_NO_WINDOW", 0) # ffmpeg/ffprobe/net sin abrir consolas negras # empaquetado como .exe con PyInstaller
|
||||||
@@ -465,7 +465,7 @@ def escanear(ruta):
|
|||||||
basura = 1 if (en_basura or rol == "basura") else 0
|
basura = 1 if (en_basura or rol == "basura") else 0
|
||||||
proxy = 1 if (en_proxy or rol == "proxy") else 0
|
proxy = 1 if (en_proxy or rol == "proxy") else 0
|
||||||
stock = 1 if (en_stock or rol == "stock") else 0
|
stock = 1 if (en_stock or rol == "stock") else 0
|
||||||
fecha, origen = R.fecha_de_nombre(nombre, st.st_mtime)
|
fecha, origen = R.fecha_de_nombre(nombre, R.fecha_real(st))
|
||||||
fy, fm = int(fecha[:4]), int(fecha[5:7])
|
fy, fm = int(fecha[:4]), int(fecha[5:7])
|
||||||
filas.append((e.path, raiz, path, nombre, ext, st.st_size, st.st_mtime, rol, firma or "",
|
filas.append((e.path, raiz, path, nombre, ext, st.st_size, st.st_mtime, rol, firma or "",
|
||||||
ctx or "", anio, mes, fecha, origen, basura, proxy, stock))
|
ctx or "", anio, mes, fecha, origen, basura, proxy, stock))
|
||||||
@@ -487,6 +487,7 @@ def escanear(ruta):
|
|||||||
try:
|
try:
|
||||||
walk(raiz, False, False, False, "", "raiz", None, None)
|
walk(raiz, False, False, False, "", "raiz", None, None)
|
||||||
prog["fallos"] = len(fallos)
|
prog["fallos"] = len(fallos)
|
||||||
|
_heredar_fechas(filas)
|
||||||
con = db()
|
con = db()
|
||||||
# Antes se borraba el inventario y se guardaba lo recogido pasara lo que pasara. Si una unidad de
|
# Antes se borraba el inventario y se guardaba lo recogido pasara lo que pasara. Si una unidad de
|
||||||
# red se caía a media lectura, el inventario quedaba truncado y marcado como bueno, y a partir de
|
# red se caía a media lectura, el inventario quedaba truncado y marcado como bueno, y a partir de
|
||||||
@@ -543,6 +544,42 @@ def escanear(ruta):
|
|||||||
prog["fin"] = time.time()
|
prog["fin"] = time.time()
|
||||||
|
|
||||||
|
|
||||||
|
def _heredar_fechas(filas):
|
||||||
|
"""Un render o una conversión se crean mucho después del rodaje, así que su fecha no dice nada del
|
||||||
|
material: si el original está en la misma carpeta, el derivado toma SU fecha.
|
||||||
|
|
||||||
|
Así «C0001_10bit.mp4» se archiva en el mes en que se grabó «C0001.MP4», no en el mes en que se
|
||||||
|
convirtió, y el mes de una carpeta se decide por el material de verdad."""
|
||||||
|
I_RUTA, I_DIR, I_NOMBRE, I_FECHA, I_ORIGEN = 0, 2, 3, 12, 13
|
||||||
|
por_dir = defaultdict(dict)
|
||||||
|
en_todo = defaultdict(list)
|
||||||
|
for f in filas:
|
||||||
|
tallo = os.path.splitext(f[I_NOMBRE])[0].lower()
|
||||||
|
por_dir[f[I_DIR].lower()][tallo] = f
|
||||||
|
en_todo[tallo].append(f)
|
||||||
|
heredadas = 0
|
||||||
|
for i, f in enumerate(filas):
|
||||||
|
base = R.base_derivado(f[I_NOMBRE])
|
||||||
|
if not base:
|
||||||
|
continue
|
||||||
|
orig = por_dir[f[I_DIR].lower()].get(base.lower())
|
||||||
|
if orig is None and len(base) >= 6:
|
||||||
|
# el render puede estar en otra carpeta («_Exportados», «_Archivo»): vale si el nombre es
|
||||||
|
# bastante propio y no hay dos candidatos que se llamen igual en toda la carpeta del cliente
|
||||||
|
cands = en_todo.get(base.lower()) or []
|
||||||
|
orig = cands[0] if len(cands) == 1 else None
|
||||||
|
if orig is None or orig is f or orig[I_ORIGEN] == "derivado":
|
||||||
|
continue
|
||||||
|
if orig[I_FECHA] >= f[I_FECHA]:
|
||||||
|
continue # el «original» no es más viejo: no hereda nada
|
||||||
|
nueva = list(f)
|
||||||
|
nueva[I_FECHA] = orig[I_FECHA]
|
||||||
|
nueva[I_ORIGEN] = "derivado" # la fecha viene del archivo del que salió
|
||||||
|
filas[i] = tuple(nueva)
|
||||||
|
heredadas += 1
|
||||||
|
return heredadas
|
||||||
|
|
||||||
|
|
||||||
def lanzar_escaneo(ruta):
|
def lanzar_escaneo(ruta):
|
||||||
raiz = os.path.normpath(ruta)
|
raiz = os.path.normpath(ruta)
|
||||||
p = PROGRESO.get(raiz)
|
p = PROGRESO.get(raiz)
|
||||||
@@ -810,7 +847,8 @@ class Plan:
|
|||||||
if nombre != canon:
|
if nombre != canon:
|
||||||
motivo = "nombre de mes normalizado" + (" (el número no coincidía con el nombre: gana el nombre)" if conflicto else "")
|
motivo = "nombre de mes normalizado" + (" (el número no coincidía con el nombre: gana el nombre)" if conflicto else "")
|
||||||
if discrepa:
|
if discrepa:
|
||||||
motivo += f" · OJO: lo que hay dentro parece de {R.nombre_mes(real[1])}"
|
motivo += (f" · ojo: lo que hay dentro se grabó en {R.nombre_mes(real[1])}, y se archiva "
|
||||||
|
f"en ese mes aunque la carpeta se llame así")
|
||||||
self.agregar(e.path, os.path.join(padre, canon), "MES", "renombrar", motivo,
|
self.agregar(e.path, os.path.join(padre, canon), "MES", "renombrar", motivo,
|
||||||
"media" if (conflicto or discrepa) else "alta")
|
"media" if (conflicto or discrepa) else "alta")
|
||||||
self.plan_dir(e.path, "mes", anio, m)
|
self.plan_dir(e.path, "mes", anio, m)
|
||||||
@@ -961,17 +999,23 @@ class Plan:
|
|||||||
elif R.detectar_mes(seg)[0]:
|
elif R.detectar_mes(seg)[0]:
|
||||||
mes_ruta = R.detectar_mes(seg)[0]
|
mes_ruta = R.detectar_mes(seg)[0]
|
||||||
if anio and mes:
|
if anio and mes:
|
||||||
# La carpeta dice un mes, pero los archivos pueden decir otro. Medido el 16-sep-2026 sobre
|
# La carpeta dice un mes y el archivo puede decir otro. Medido el 16-sep-2026 sobre el
|
||||||
# el material real: 5 de las 10 carpetas de mes con material fechable no contenían el mes
|
# material real: 5 de las 10 carpetas de mes con material fechable no contenían el mes que
|
||||||
# que anunciaban (una llamada «02-marzo» tenía 119 clips de diciembre). Antes esto se movía
|
# anunciaban (una llamada «02-marzo» tenía 119 clips de diciembre de 2025).
|
||||||
# con confianza alta y sin aviso; ahora se avisa y baja a media.
|
#
|
||||||
|
# Manda el archivo, no la carpeta: el nombre lo pone la cámara y la fecha de creación la
|
||||||
|
# pone el disco, mientras que el nombre de la carpeta lo escribió una persona con prisa.
|
||||||
|
# Así no hay que preguntarle a nadie cuál es el mes bueno.
|
||||||
ay, am, fconf = anio, mes, "alta"
|
ay, am, fconf = anio, mes, "alta"
|
||||||
if _discrepa_mes(f["fecha"], f["fecha_origen"], anio, mes):
|
if _discrepa_mes(f["fecha"], f["fecha_origen"], anio, mes):
|
||||||
fconf = "media"
|
ra, rm = int(f["fecha"][:4]), int(f["fecha"][5:7])
|
||||||
desacuerdo[(int(f["fecha"][:4]), int(f["fecha"][5:7]))] += 1
|
if f["fecha_origen"] == "nombre+mtime":
|
||||||
|
ra = anio if rm <= mes else anio - 1 # el nombre de la cámara no trae año
|
||||||
|
ay, am, fconf = ra, rm, "media"
|
||||||
|
desacuerdo[(ra, rm)] += 1
|
||||||
else:
|
else:
|
||||||
ay, am = int(f["fecha"][:4]), int(f["fecha"][5:7])
|
ay, am = int(f["fecha"][:4]), int(f["fecha"][5:7])
|
||||||
por_nombre = f["fecha_origen"] != "mtime"
|
por_nombre = f["fecha_origen"] != "mtime" # «nombre», «nombre+mtime» o «derivado»
|
||||||
fconf = "media" if por_nombre else "baja"
|
fconf = "media" if por_nombre else "baja"
|
||||||
# el año de una carpeta intermedia ("…/2023/…") manda sobre la fecha de modificación,
|
# el año de una carpeta intermedia ("…/2023/…") manda sobre la fecha de modificación,
|
||||||
# pero no sobre una fecha escrita en el nombre del archivo
|
# pero no sobre una fecha escrita en el nombre del archivo
|
||||||
@@ -994,10 +1038,11 @@ class Plan:
|
|||||||
n_fechables = sum(1 for f in filas if not f["es_basura"] and not f["es_proxy"] and f["fecha_origen"] != "mtime")
|
n_fechables = sum(1 for f in filas if not f["es_basura"] and not f["es_proxy"] and f["fecha_origen"] != "mtime")
|
||||||
if desacuerdo and sum(desacuerdo.values()) >= 3 and sum(desacuerdo.values()) >= 0.25 * max(1, n_fechables):
|
if desacuerdo and sum(desacuerdo.values()) >= 3 and sum(desacuerdo.values()) >= 0.25 * max(1, n_fechables):
|
||||||
(da, dm), cuantos = desacuerdo.most_common(1)[0]
|
(da, dm), cuantos = desacuerdo.most_common(1)[0]
|
||||||
aviso_fecha = (f" · OJO: la carpeta dice {R.nombre_mes(mes)} pero {cuantos} de sus archivos son de "
|
aviso_fecha = (f" · la carpeta decía {R.nombre_mes(mes)}, pero {cuantos} de sus archivos son de "
|
||||||
f"{R.nombre_mes(dm)} según su propio nombre; comprueba antes de aplicar")
|
f"{R.nombre_mes(dm)} de {da}: van a su mes de verdad")
|
||||||
self.avisos.append(f"«{self.rel(e.path)}» dice {R.nombre_mes(mes)} de {anio} y el nombre de {cuantos} de "
|
self.avisos.append(f"«{self.rel(e.path)}» está dentro de {R.nombre_mes(mes)} de {anio}, pero {cuantos} de "
|
||||||
f"sus archivos dice {R.nombre_mes(dm)}: se propone sin marcar para que lo revises.")
|
f"sus archivos se grabaron en {R.nombre_mes(dm)} de {da}. Se archivan en el mes en que "
|
||||||
|
f"se grabaron, no en el que dice la carpeta.")
|
||||||
for (ay, am, fuente, fconf), archivos in grupos.items():
|
for (ay, am, fuente, fconf), archivos in grupos.items():
|
||||||
sub = fuente if not etiqueta else os.path.join(etiqueta, fuente)
|
sub = fuente if not etiqueta else os.path.join(etiqueta, fuente)
|
||||||
destino = destino_plantilla(self.tipo, "GRABACION", anio=ay, mes=am, sub=sub, nombre=limpiar_nombre(nombre))
|
destino = destino_plantilla(self.tipo, "GRABACION", anio=ay, mes=am, sub=sub, nombre=limpiar_nombre(nombre))
|
||||||
@@ -1005,9 +1050,8 @@ class Plan:
|
|||||||
if all(os.path.normpath(os.path.dirname(a[0])).lower() == self.abs_(destino).lower() for a in archivos):
|
if all(os.path.normpath(os.path.dirname(a[0])).lower() == self.abs_(destino).lower() for a in archivos):
|
||||||
continue
|
continue
|
||||||
mot = motivo + (" · repartido por fuente" if anio and mes else " · mes por la fecha de cada archivo" + (" (fecha de modificación)" if fconf == "baja" else ""))
|
mot = motivo + (" · repartido por fuente" if anio and mes else " · mes por la fecha de cada archivo" + (" (fecha de modificación)" if fconf == "baja" else ""))
|
||||||
marcado = False if aviso_fecha else None
|
|
||||||
self.agregar(e.path, self.abs_(destino), "GRABACION", "mover archivos", mot + aviso_fecha, fconf,
|
self.agregar(e.path, self.abs_(destino), "GRABACION", "mover archivos", mot + aviso_fecha, fconf,
|
||||||
archivos=archivos, marcado=marcado)
|
archivos=archivos)
|
||||||
|
|
||||||
def plan_archivo(self, e, nivel, anio, mes, padre):
|
def plan_archivo(self, e, nivel, anio, mes, padre):
|
||||||
fila = self.con.execute("SELECT * FROM archivos WHERE ruta = ?", (e.path,)).fetchone()
|
fila = self.con.execute("SELECT * FROM archivos WHERE ruta = ?", (e.path,)).fetchone()
|
||||||
|
|||||||
27
reglas.py
27
reglas.py
@@ -9,6 +9,7 @@ estructura canónica a la que se lleva cada cosa según el tipo de cliente.
|
|||||||
El diccionario de carpetas se siembra desde aquí la primera vez y después
|
El diccionario de carpetas se siembra desde aquí la primera vez y después
|
||||||
se guarda en datos/diccionario.json, donde la app va aprendiendo.
|
se guarda en datos/diccionario.json, donde la app va aprendiendo.
|
||||||
"""
|
"""
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import unicodedata
|
import unicodedata
|
||||||
import datetime
|
import datetime
|
||||||
@@ -257,6 +258,32 @@ _RX_BM = re.compile(r"^[A-Z]\d{3}_(\d{2})(\d{2})\d{4}_C\d{3}", re.I)
|
|||||||
_RX_WA = re.compile(r"WhatsApp \w+ (20\d{2})-(\d{2})-(\d{2})", re.I)
|
_RX_WA = re.compile(r"WhatsApp \w+ (20\d{2})-(\d{2})-(\d{2})", re.I)
|
||||||
|
|
||||||
|
|
||||||
|
# sufijos que la propia app (o el editor) le pone a un archivo derivado de otro: el derivado se
|
||||||
|
# genera mucho después, pero la fecha que importa es la del material original
|
||||||
|
SUFIJOS_DERIVADO = ("_10bit", "_ligero", "_h264", "_proxy", "_lt", "_hq", "_prores", "_convertido",
|
||||||
|
"_export", "_render", "_master", "_final")
|
||||||
|
|
||||||
|
|
||||||
|
def base_derivado(nombre: str):
|
||||||
|
"""Nombre del archivo del que este parece salir, o None. «C0001_10bit.mp4» -> «C0001»."""
|
||||||
|
base = os.path.splitext(nombre)[0]
|
||||||
|
b = base.lower()
|
||||||
|
for suf in SUFIJOS_DERIVADO:
|
||||||
|
if b.endswith(suf) and len(base) > len(suf):
|
||||||
|
return base[:-len(suf)]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def fecha_real(st):
|
||||||
|
"""El instante más antiguo que el disco sabe de este archivo.
|
||||||
|
|
||||||
|
Al copiar en Windows, la fecha de creación pasa a ser la de la copia y la de modificación se
|
||||||
|
conserva; al renderizar o reexportar, las dos son nuevas. Quedarse con la más antigua de las dos
|
||||||
|
da siempre la mejor estimación de cuándo se grabó, sin tener que elegir una de antemano."""
|
||||||
|
c = getattr(st, "st_birthtime", None) or st.st_ctime
|
||||||
|
return min(st.st_mtime, c) if c and c > 0 else st.st_mtime
|
||||||
|
|
||||||
|
|
||||||
def fecha_de_nombre(nombre: str, mtime: float):
|
def fecha_de_nombre(nombre: str, mtime: float):
|
||||||
"""(YYYY-MM-DD, origen) — origen 'nombre' si sale del nombre, 'mtime' si no."""
|
"""(YYYY-MM-DD, origen) — origen 'nombre' si sale del nombre, 'mtime' si no."""
|
||||||
for rx in (_RX_YMD, _RX_YMD_HMS, _RX_Y_M_D, _RX_WA):
|
for rx in (_RX_YMD, _RX_YMD_HMS, _RX_Y_M_D, _RX_WA):
|
||||||
|
|||||||
Referencia en una nueva incidencia
Block a user