=====================================================
  RISUS NOVA 2.1.5 — CHANGELOG
  PHPost | https://phpost.es/
  Fecha: 19 de Julio 2026
  @author  Tronlar + PHPost Team
=====================================================


--------------------------------------------------------
  FIX — CONEXION A LA BASE DE DATOS CON PUERTO PERSONALIZADO
--------------------------------------------------------
- La conexion usaba siempre el puerto por defecto sin
  posibilidad de indicar uno distinto, lo que daba
  problemas en alojamientos que requieren un puerto
  MySQL fuera del estandar.
- Ahora se puede anadir opcionalmente $db['port'] en
  config.inc.php; si no se define, todo sigue
  funcionando exactamente igual que antes.
- Modificado: functions.php


--------------------------------------------------------
  FIX — INSTALADOR PODIA QUEDAR BLOQUEADO TRAS UN FALLO
--------------------------------------------------------
- El archivo install.lock se creaba siempre al terminar
  el proceso, incluso si habia errores reales durante la
  instalacion, dejando al usuario sin poder reintentar
  sin borrar ese archivo manualmente por FTP.
- Ahora solo se crea cuando la instalacion termina sin
  errores.
- Modificado: install/index.php


--------------------------------------------------------
  FIX — AVISOS NORMALES DE LA BASE DE DATOS CONTADOS COMO ERRORES
--------------------------------------------------------
- Al ejecutar de nuevo alguna sentencia ya aplicada,
  MySQL/MariaDB respondia con avisos habituales (columna
  o indice ya existente) que el instalador interpretaba
  como fallos criticos, inflando el contador final de
  errores sin que hubiera ningun problema real.
- Ampliado el filtro para reconocer estos avisos como lo
  que son.
- Modificado: install/index.php


--------------------------------------------------------
  FIX — TITULO REFORZADO AL CREAR UN POST NUEVO
--------------------------------------------------------
- Al crear un post nuevo, el titulo no pasaba por el
  mismo tratamiento que ya se le aplicaba al editar un
  post existente, dejando una inconsistencia entre ambos
  flujos.
- Ahora los dos casos procesan el titulo de la misma
  forma.
- Modificado: c.posts.php


--------------------------------------------------------
  MEJORA — INSTALADOR REFORZADO
--------------------------------------------------------
- El instalador ahora se bloquea automaticamente en
  cuanto termina de instalarse con exito (igual que
  hacen otros scripts conocidos), para evitar que
  alguien pueda volver a ejecutarlo por error o con
  mala intencion una vez el sitio ya esta en marcha.
- Tambien comprueba desde el principio la version de
  PHP del servidor: si es demasiado antigua, avisa con
  un mensaje claro pidiendo actualizarla, en vez de
  fallar a medias durante la instalacion.
- Modificado: install/index.php


--------------------------------------------------------
  FIX — REFUERZO EN EL BUSCADOR DE COMUNIDADES
--------------------------------------------------------
- Reforzado como se muestra el texto de busqueda en los
  resultados y en la paginacion, para que lo que
  escribas siempre se vea tal cual y no pueda alterar
  el resto de la pagina.
- Modificado: c.buscar_left.tpl


--------------------------------------------------------
  FIX — REFUERZOS EN EL MODULO DE SUGERENCIAS
--------------------------------------------------------
- Corregida una ruta interna que hacia que la pagina de
  detalle de una sugerencia no cargara desde el sitio
  correcto.
- Reforzado como se muestran el titulo, el cuerpo y la
  respuesta oficial de cada sugerencia, para que el
  contenido se vea siempre tal cual se escribio, sin
  que ningun caracter especial pueda alterar el resto
  de la pagina.
- Modificado: sugerencias.tpl, m.sugerencias_listado.tpl,
  p.sugerencias.detalle.tpl


--------------------------------------------------------
  FIX — REFUERZOS EN EL MURO DE PERFIL
--------------------------------------------------------
- Reforzada la validacion de las direcciones de las
  imagenes ampliadas del perfil y de los adjuntos del
  muro (fotos, enlaces y videos), asegurando que el
  contenido que se muestra siempre sea el esperado.
- Afinadas un par de consultas a la base de datos para
  que acepten unicamente valores numericos donde
  corresponde.
- Corregido que al borrar una publicacion con varios
  comentarios no se limpiaran del todo los "me gusta"
  asociados.
- Modificado: c.muro.php, m.perfil_muro_story.tpl,
  perfil.js


--------------------------------------------------------
  FIX — GUARDADO DE COMENTARIOS EN POSTS
--------------------------------------------------------
- Reforzado el guardado del texto del comentario en la
  base de datos.
- Modificado: c.posts.php


--------------------------------------------------------
  FIX — SE PODIA REACCIONAR AL PROPIO POST
--------------------------------------------------------
- El sistema de reacciones (Me gusta, Me encanta, etc.)
  no comprobaba que el usuario no fuera el autor del
  post, tanto en la interfaz como en el guardado en
  servidor.
- Corregido en ambos lados: la interfaz ya no muestra el
  boton interactivo en tu propio post (solo el contador,
  a modo informativo), y el servidor rechaza la peticion
  aunque se intente saltar la interfaz.
- Modificado: c.posts.php, m.posts_metadata.tpl


--------------------------------------------------------
  FIX — ENDURECIMIENTO DE UNSERIALIZE() EN HISTORIAL DE
  VISITAS DEL PORTAL
--------------------------------------------------------
- unserialize() sin allowed_classes en el listado de
  ultimos posts visitados del portal.
- Corregido anadiendo allowed_classes => false.
- Modificado: c.posts.php


--------------------------------------------------------
  NUEVO — MODULO OFERTAS DE STEAM
--------------------------------------------------------
- Nueva seccion /steam/ con un slider de las ultimas
  ofertas de Steam, obtenidas gratis via la API de
  CheapShark (sin necesidad de clave de API)
- Los datos se cachean en su propia tabla y se refrescan
  solos por cron cada X horas configurables, ninguna
  visita depende de que CheapShark responda
- Se activa desde Admin > Configuracion > pestana
  Ofertas de Steam, y el refresco automatico desde
  Admin > Cron Jobs
- Modificado: database.php, c.admin.php,
  m.admin_configs.tpl, index.php, cron.php
- Nuevos: c.steam.php, steam.php, t.steam.tpl, steam.css


--------------------------------------------------------
  SEGURIDAD — SALIDA DE HTML SIN SANEAR EN EL CUERPO
  DEL POST Y EN LA FIRMA DE USUARIO
--------------------------------------------------------
- Ambos campos se imprimian directamente sin pasar por
  ninguna limpieza adicional antes de mostrarse.
- Anadida una capa de sanitizacion de HTML en el momento
  de mostrarlos.
- Modificado: m.posts_content.tpl
- Nuevo: modifier.clean_html.php


--------------------------------------------------------
  NUEVO — MODULO RADIO
--------------------------------------------------------
- Nueva seccion /radio/ con reproductor de audio en
  directo y el Shoutbox integrado al lado, compatible
  con Icecast, Shoutcast y Zeno.FM
- Se configura desde Admin > Configuracion > pestana
  Radio: elige el sistema, pega la URL del stream (o el
  ID de Zeno.FM) y guarda
- La pestana del menu aparece sola en cuanto hay algo
  configurado
- Incluye README de instalacion para instalaciones nuevas
- Modificado: database.php, c.admin.php,
  m.admin_configs.tpl, index.php, .htaccess,
  head_menu.tpl, m.home_shoutbox.tpl
- Nuevos: radio.php, radio-nowplaying.php, t.radio.tpl,
  radio.css


--------------------------------------------------------
  MEJORA — CSS INLINE ELIMINADO DE VARIOS WIDGETS DE LA
  HOME Y DEL LOGO
--------------------------------------------------------
- Los estilos que iban sueltos en style="" dentro de las
  plantillas se movieron a clases centralizadas en
  estilo.css, facilitando futuros cambios de tema y
  manteniendo el HTML mas limpio
- De paso se corrigieron un par de conflictos de
  especificidad que habia con reglas antiguas de
  estilo.css (numeracion e indentado inesperados en las
  listas de Top Posts y Top Usuarios)
- Modificado: m.home_stats.tpl, m.home_last_comments.tpl,
  m.home_top_posts.tpl, m.home_top_users.tpl,
  m.home_ultimos_registrados.tpl, m.home_fotos.tpl,
  m.home_topcat.tpl, logo.tpl, m.home_misiones.tpl,
  m.home_calendario.tpl, m.home_retos.tpl, estilo.css


--------------------------------------------------------
  MEJORA — SIMPLIFICACION DEL SISTEMA DE SMART BANNERS
--------------------------------------------------------
- El campo cta_css de cada variante de banner era
  redundante (siempre valia lo mismo que el campo color
  ya existente)
- Eliminado del backend y el boton de accion del banner
  ahora toma su color directamente de ese campo, sin
  cambios visuales
- Modificado: c.smartbanner.php, m.smart_banner.tpl


--------------------------------------------------------
  MEJORA — INSERTAR FICHAS DE JUEGOS DE
  RETROACHIEVEMENTS EN EL EDITOR
--------------------------------------------------------
- Nueva herramienta en el editor de posts para insertar
  la ficha de un juego pegando el link de su pagina en
  RetroAchievements (ej.
  retroachievements.org/game/14402)
- Misma idea que la herramienta ya existente de TMDB
  para peliculas y series, pero por link en vez de
  busqueda por titulo, ya que la API publica de
  RetroAchievements no ofrece busqueda por texto
- La ficha incluye titulo, consola, desarrollador,
  editor y cantidad de logros. Requiere configurar una
  clave gratuita de API en Admin -> Configuracion ->
  RetroAchievements
- De paso se corrigio un bug existente: el campo de
  clave de TMDB nunca se guardaba al hacer clic en
  Guardar en el panel de admin -- el formulario lo
  mostraba pero el backend no lo leia ni escribia
- Modificado: database.php, main_header.tpl,
  m.admin_configs.tpl, m.agregar_form.tpl, funciones.js,
  acciones.js, c.admin.php


--------------------------------------------------------
  FIX — REDIMENSIONADO DINAMICO DE IMAGENES EN POSTS,
  MURO Y AVATAR
--------------------------------------------------------
- Las imagenes ya alojadas en Cloudinary se servian
  siempre a tamano completo, sin importar donde se
  mostraran
- Agregado un helper que inyecta la transformacion de
  ancho/calidad/formato de Cloudinary al vuelo (800px en
  posts, 500px en el muro), sin afectar imagenes locales
  o externas
- De paso, el recorte de avatar ahora sube una sola
  version a Cloudinary en vez de dos, derivando
  cualquier tamano adicional del mismo helper
- Modificado: c.core.php, c.posts.php, c.muro.php,
  c.upload.php


--------------------------------------------------------
  SEGURIDAD — INYECCION SQL EN EL REGISTRO DE
  ACTIVIDAD DE USUARIO (HALLAZGO CRITICO)
--------------------------------------------------------
- El registro de actividad de usuario (seguir/dejar de
  seguir, etc.) tenia una vulnerabilidad de inyeccion
  SQL alcanzable por cualquier miembro con sesion
  iniciada.
- Corregida con validacion estricta de los parametros
  involucrados.
- Modificado: c.actividad.php


--------------------------------------------------------
  FIX — DEFENSA ADICIONAL EN CONSULTAS DE ACTIVIDAD
--------------------------------------------------------
- Mismo patron (parametros sin castear) en
  getActividad() y getActividadFollows()
- Los llamadores ya casteaban antes de invocarlas, pero
  se blindaron las funciones mismas
- Modificado: c.actividad.php


--------------------------------------------------------
  SEGURIDAD — INYECCION SQL EN EL GUARDADO DE FILTROS
  DE NOTIFICACIONES (HALLAZGO CRITICO)
--------------------------------------------------------
- El sistema de filtros del panel de notificaciones
  tenia una vulnerabilidad de inyeccion SQL al guardar
  las preferencias del usuario. Corregida con el
  escapado correcto.
- Modificado: c.monitor.php


--------------------------------------------------------
  SEGURIDAD — VALIDACION INSUFICIENTE DE PARAMETROS EN
  AVISOS Y NOTIFICACIONES
--------------------------------------------------------
- Algunos parametros del sistema de avisos y
  notificaciones no se validaban con el rigor necesario
  antes de usarse en consultas. Corregidos.
- Modificado: c.monitor.php


--------------------------------------------------------
  FIX — TYPEERROR FATAL Y BUGS MENORES EN EL SISTEMA
  DE NOTIFICACIONES
--------------------------------------------------------
- setFollowNotificacion(): in_array($val, null) es
  TypeError fatal en PHP 8 si no se pasa el parametro de
  exclusiones
- getFollowVars(): sin caso por defecto, dejaba
  type/notUser indefinidos con un valor de POST
  inesperado
- Tambien se endurecieron 2 unserialize() sin
  allowed_classes
- Modificado: c.monitor.php


--------------------------------------------------------
  SEGURIDAD — COMPARACIONES DE HASH CON UN METODO
  INSEGURO EN CAMBIO DE CONTRASENA Y DE NICK
--------------------------------------------------------
- Mismo tipo de problema que el corregido en el sistema
  de login, en los flujos de cambio de contrasena y
  cambio de nick. Corregidos con el mismo criterio.
- Modificado: c.cuenta.php


--------------------------------------------------------
  FIX — VARIABLE INEXISTENTE ROMPIA EL REGISTRO DE
  VISITAS REPETIDAS A UN PERFIL
--------------------------------------------------------
- loadHeadInfo($user_id) usaba $post_id en la rama de
  actualizar visita existente — variable que no existe
  en ningun lado de la funcion
- El UPDATE terminaba apuntando siempre a for = '0',
  nunca actualizando el registro real
- Corregido usando $user_id
- Modificado: c.cuenta.php


--------------------------------------------------------
  FIX — ENDURECIMIENTO DE UNSERIALIZE() EN PERFIL DE
  USUARIO
--------------------------------------------------------
- 8 llamadas a unserialize() sin allowed_classes
  repartidas en loadPerfil(), unData() y loadHeadInfo()
- Todas con chequeo is_array() agregado tambien para
  evitar warnings con datos corruptos
- Modificado: c.cuenta.php


--------------------------------------------------------
  FIX — AVISO DE CAMBIO DE NICK SIN ESCAPAR ANTES DE
  GUARDARSE
--------------------------------------------------------
- Por defensa adicional, aunque el nick ya viene
  validado como alfanumerico en otro punto del mismo
  flujo
- Modificado: c.cuenta.php


--------------------------------------------------------
  SEGURIDAD — REUTILIZACION SIN VALIDAR DE UN VALOR DE
  LA BASE DE DATOS EN NOTIFICACIONES DE PREMIO
--------------------------------------------------------
- La notificacion enviada al asignar un premio
  reutilizaba un valor ya almacenado en la base de datos
  sin volver a validarlo antes de una segunda consulta.
  Corregido.
- Modificado: c.premios.php


--------------------------------------------------------
  FIX — GUARDS DE VALIDACION DE ACCION FALTANTES EN 5
  CONTROLADORES AJAX
--------------------------------------------------------
- Mismo patron de siempre: $files[$action] sin comprobar
  antes que la key existiera
- Confirmado 1:1 con el switch en cada caso antes de
  aplicar
- Modificado: ajax.perfil.php, ajax.chat.php,
  ajax.notificaciones.php, ajax.favoritos.php,
  ajax.bloqueos.php


--------------------------------------------------------
  FIX — RIESGO DE ERROR 500 AL VER CUALQUIER PERFIL
  CON DATOS DE "GUSTOS" INCOMPLETOS
--------------------------------------------------------
- foreach() sobre $tsPerfil['p_gustos'] sin garantia de
  ser array — en PHP 8 eso es fatal
- Es la pagina publica de ver perfil, cualquiera podria
  gatillarlo
- Corregido con doble chequeo (is_array + fallback)
- Modificado: ajax.perfil.php


--------------------------------------------------------
  FIX — IDS SIN CASTEAR EN COMENTARIOS Y CHAT
--------------------------------------------------------
- postid se trataba como string (solo setSecure()) en
  vez de castear a (int) en 2 lugares de comentarios
- Y lo mismo con desde_id/id en el chat en tiempo real
- Modificado: ajax.comentario.php, ajax.chat.php


--------------------------------------------------------
  SEGURIDAD — COMPARACIONES DE HASH CON UN METODO
  INSEGURO EN LOGIN Y ACTIVACION DE CUENTA
--------------------------------------------------------
- Dos comparaciones de contrasena/clave (para cuentas
  con hash antiguo aun no migrado) usaban un metodo de
  comparacion de PHP que puede ser inseguro en casos
  especificos poco frecuentes.
- Corregidas usando la funcion de comparacion segura
  recomendada.
- Modificado: c.user.php


--------------------------------------------------------
  SEGURIDAD — INYECCION SQL EN EL CIERRE DE SESION
--------------------------------------------------------
- El proceso de cierre de sesion tenia una
  vulnerabilidad de inyeccion SQL, alcanzable sin
  necesitar tener una sesion valida. Corregida escapando
  correctamente el valor involucrado.
- Modificado: c.user.php


--------------------------------------------------------
  FIX — ENDURECIMIENTO ADICIONAL EN MANEJO DE SESION
--------------------------------------------------------
- Mismo escapado defensivo aplicado tambien en update()
  y loadUser() (no explotables en el flujo actual, pero
  por consistencia)
- Agregado allowed_classes=>false al unserialize() de
  permisos de rango
- Modificado: c.user.php


--------------------------------------------------------
  FIX — TOOLTIP INCORRECTO EN "POSTS EN TENDENCIA" DEL
  HOME
--------------------------------------------------------
- La etiqueta <a> del titulo del post quedaba con class
  y title duplicados cuando el post tenia algun estado
  especial (en revision, eliminado, cuenta
  desactivada/suspendida)
- Los navegadores usan el primer atributo cuando hay
  duplicados, tapando el titulo real del post con el
  mensaje de estado
- Corregido consolidando en un solo class y un solo
  title
- Modificado: m.home_tendencias.tpl


--------------------------------------------------------
  FIX — TOOLTIP DE TENDENCIAS MOSTRABA VACIO EN VEZ
  DEL TITULO REAL
--------------------------------------------------------
- getTendencias() filtraba por user_activo y
  user_baneado en el WHERE/JOIN pero nunca los incluia
  en el SELECT, dejando esas variables indefinidas en la
  plantilla
- Corregido agregandolas a la consulta
- Modificado: c.posts.php


--------------------------------------------------------
  SEGURIDAD — VULNERABILIDAD EN EL RECORTE DE AVATAR
  PERMITIA OPERAR SOBRE ARCHIVOS NO AUTORIZADOS
  (HALLAZGO CRITICO)
--------------------------------------------------------
- La funcion de recorte de avatar no validaba con
  suficiente rigor los datos recibidos antes de operar
  sobre archivos del servidor. Corregido con validacion
  estricta de todos los parametros involucrados.
- Modificado: c.upload.php


--------------------------------------------------------
  SEGURIDAD — RESTRICCION INSUFICIENTE DE DESTINOS EN
  "SUBIR AVATAR POR URL"
--------------------------------------------------------
- La funcion de subir avatar mediante una URL externa no
  restringia adecuadamente los destinos de red
  permitidos. Corregido bloqueando esquemas y rangos de
  IP no validos.
- Modificado: c.upload.php


--------------------------------------------------------
  SEGURIDAD — ACCION DE ADMINISTRACION DE ARCHIVOS SIN
  NIVEL DE ACCESO CONFIGURADO
--------------------------------------------------------
- Una de las acciones de gestion de archivos subidos no
  tenia correctamente configurado el nivel de acceso
  requerido. Corregido.
- Modificado: ajax.upload.php


--------------------------------------------------------
  FIX — GUARDS DE VALIDACION DE ACCION FALTANTES EN
  VARIOS CONTROLADORES AJAX
--------------------------------------------------------
- Mismo patron en 7 archivos: se leia $files[$action]
  sin comprobar antes que la key existiera, generando
  comportamiento indefinido con una accion desconocida o
  mal escrita
- Agregado el guard isset() correspondiente en cada uno
- Modificado: ajax.usuario.php, ajax.denuncia.php,
  ajax.moderacion.php, ajax.registro.php,
  ajax.mensajes.php, ajax.posts.php, ajax.muro.php


--------------------------------------------------------
  SEGURIDAD — VALIDACION INSUFICIENTE DE PARAMETROS EN
  ACCIONES DE MODERACION
--------------------------------------------------------
- Varios parametros usados en las acciones de moderacion
  (posts, usuarios, comunidades, temas, mensajes, fotos)
  no se validaban con el rigor necesario antes de
  usarse.
- Corregidos, junto con dos bugs funcionales encontrados
  de paso.
- Modificado: ajax.moderacion.php


--------------------------------------------------------
  FIX — FOREACH() SOBRE NULL PODIA TIRAR ERROR 500 AL
  CARGAR PROVINCIAS EN EL REGISTRO
--------------------------------------------------------
- Con un codigo de pais invalido o faltante,
  $estados[$pais] es null, y en PHP 8 foreach() sobre
  null es un error fatal (antes solo un warning)
- Corregido comprobando que el pais exista antes de
  iterar
- Modificado: ajax.registro.php


--------------------------------------------------------
  SEGURIDAD — CONTRASENA EN TEXTO PLANO EN EL EMAIL DE
  BIENVENIDA
--------------------------------------------------------
- El correo de activacion de cuenta incluia la
  contrasena elegida por el usuario en texto plano, una
  mala practica de seguridad conocida. Corregido
  quitandola del cuerpo del correo.
- Modificado: c.registro.php


--------------------------------------------------------
  FIX — REGISTRO Y RECUPERACION DE CONTRASENA ROTOS
  CON FATAL ERROR — HALLAZGO CRITICO
--------------------------------------------------------
- Ambos flujos llamaban a $tsEmail->setEmailHeaders(),
  un metodo que no existe en la clase de emails
  reescrita (con soporte SMTP)
- Ademas usaban la convencion vieja del constructor
  (strings sueltos en vez de un array de datos + un ref
  valido), por lo que ni siquiera con el metodo
  corregido se hubiera armado el email correctamente
- El registro con confirmacion por email y la
  recuperacion de contrasena estaban completamente rotos
- Corregidos los 3 puntos de envio usando la API actual
  de la clase
- Modificado: c.registro.php, ajax.recover.php


--------------------------------------------------------
  SEGURIDAD — CONFIGURACION DE ACCESO INCOMPLETA EN EL
  MODULO DE COMUNIDADES
--------------------------------------------------------
- Varias acciones del modulo de comunidades no tenian
  correctamente configurado el nivel de acceso
  requerido. Corregido completando la configuracion
  faltante.
- Modificado: ajax.comunidades.php


--------------------------------------------------------
  SEGURIDAD — XSS REFLEJADO Y FUGA DE INFORMACION EN
  EL MANEJO DE ERRORES DE LA BASE DE DATOS
--------------------------------------------------------
- El panel de depuracion de errores de base de datos no
  escapaba correctamente cierta informacion, y un fallo
  de conexion exponia detalles internos a cualquier
  visitante. Corregidos ambos problemas.
- Modificado: functions.php


--------------------------------------------------------
  FIX — SIN LIMITE DE TAMANO EXPLICITO ANTES DE
  PROCESAR IMAGENES SUBIDAS
--------------------------------------------------------
- Dependia por completo de la configuracion de php.ini
- Agregado un limite de 10MB como defensa adicional
- Modificado: functions.php


--------------------------------------------------------
  SEGURIDAD — XSS ALMACENADO EN EL BBCODE DE CITA
  (QUOTE)
--------------------------------------------------------
- El nombre del autor citado no pasaba por ninguna
  validacion antes de insertarse en el HTML generado.
  Agregada la validacion correspondiente, igual que ya
  tenian el resto de los parametros del parser.
- Modificado: bbcode_inc.php,
  JBBCode/validators/QuoteAuthorValidator.php


--------------------------------------------------------
  FIX — BUG DE REGEX EN LA NORMALIZACION DE LINKS
  VIEJOS DE YOUTUBE
--------------------------------------------------------
- [\.com]+/v/ es una clase de caracteres (matchea
  cualquier combinacion de esas 4 letras), no el literal
  .com, causando reemplazos en texto no relacionado
- Corregido a \.com/v/
- Modificado: bbcode_inc.php


--------------------------------------------------------
  SEGURIDAD — POSIBLE REINTRODUCCION DE HTML SIN
  ESCAPAR EN LA VERSION EN TEXTO PLANO DE UN POST
--------------------------------------------------------
- La generacion de la version en texto plano de un post
  podia, en ciertos casos, reintroducir HTML que ya
  habia sido neutralizado. Corregido el orden de
  procesamiento.
- Modificado: bbcode_inc.php


--------------------------------------------------------
  SEGURIDAD — DEBILIDAD EN LA VERIFICACION DE ORIGEN
  USADA PARA PROTEGER ADMIN/MODERACION/CUENTA
--------------------------------------------------------
- La comprobacion de que una peticion proviene del
  propio sitio (una capa de proteccion usada en varias
  acciones sensibles) tenia una debilidad en su logica
  de comparacion. Corregida.
- Modificado: QueryString.php


--------------------------------------------------------
  SEGURIDAD — XSS REFLEJADO Y ENDPOINT SIN
  AUTENTICACION EN LOS SUBIDORES DE IMAGENES DEL
  EDITOR
--------------------------------------------------------
- Los subidores de imagenes del editor tenian una
  vulnerabilidad de XSS reflejado, y uno de los dos
  archivos carecia del control de acceso que si tiene el
  resto del sitio. Corregidos ambos.
- Modificado: wysibbupload.php, iupload.php


--------------------------------------------------------
  SEGURIDAD — MENSAJES PRIVADOS: FILTRADO INSUFICIENTE
  DEL CONTENIDO AL MOSTRARSE
--------------------------------------------------------
- El cuerpo de los mensajes privados no pasaba por el
  filtrado de seguridad adecuado antes de mostrarse.
  Corregido con el mismo criterio ya usado en otras
  partes del sitio.
- Modificado: c.mensajes.php


--------------------------------------------------------
  SEGURIDAD — CONTENIDO DEL EDITOR CKEDITOR EN POSTS Y
  COMENTARIOS SE GUARDABA SIN SANITIZAR (HALLAZGO
  CRITICO)
--------------------------------------------------------
- El contenido enviado desde el editor de texto
  enriquecido (CKEditor) en posts y comentarios no
  pasaba por ningun filtro de seguridad del lado del
  servidor antes de guardarse y mostrarse.
- Se instalo la libreria HTMLPurifier para sanitizar
  correctamente este contenido en los cuatro puntos
  donde ocurria.
- Modificado: c.posts.php, inc/ext/htmlpurifier/


--------------------------------------------------------
  SEGURIDAD — MISMO HUECO DE SANITIZACION DE CKEDITOR
  EN RESPUESTAS DE COMUNIDADES
--------------------------------------------------------
- Mismo problema que el hallazgo anterior, en las
  respuestas de foro dentro de comunidades. Corregido
  con la misma solucion.
- Modificado: c.comunidades.php


--------------------------------------------------------
  FIX — IP FALSEABLE Y SUPERGLOBAL SOBRESCRITA EN
  COMUNIDADES, REPETIDO 5 VECES
--------------------------------------------------------
- $_SERVER['X_FORWARDED_FOR'] no existe como tal en PHP
  (falta el prefijo HTTP_), por lo que el chequeo nunca
  funcionaba como estaba pensado, y de paso sobrescribia
  la superglobal $_SERVER['REMOTE_ADDR'] para el resto
  del request
- Repetido identico en 5 lugares del archivo
- Corregido usando $tsCore->getIP(), el helper ya usado
  en el resto del codigo
- De paso se corrigio un typo de variable-variable
  ($$temaid en vez de $temaid) encontrado en el mismo
  archivo
- Modificado: c.comunidades.php


--------------------------------------------------------
  SEGURIDAD — XSS ALMACENADO EN LA VISTA PREVIA DE
  ENLACES DEL EDITOR DE POSTS
--------------------------------------------------------
- La tarjeta de previsualizacion que se genera al pegar
  un enlace en el editor no escapaba correctamente todos
  sus campos, permitiendo XSS almacenado. Corregido.
- Modificado: link-preview.js


--------------------------------------------------------
  SEGURIDAD — INYECCION SQL EN "MIS CATEGORIAS" DEL
  PORTAL
--------------------------------------------------------
- Los IDs de categoria guardados por el usuario no se
  validaban con el rigor necesario antes de usarse en
  una consulta. Corregido con validacion estricta.
- Modificado: c.portal.php


--------------------------------------------------------
  COMPAT — ERRORES 500 EN PHP 8.2 AL EDITAR EL PERFIL
--------------------------------------------------------
- Guardar el perfil con algun checkbox de "Me gustaria"
  sin marcar producia un TypeError fatal, porque los
  checkboxes no marcados no viajan en el $_POST y PHP
  8.2 no acepta null donde se espera string
- Guardar "Sitio Web" fallaba porque
  FILTER_FLAG_HOST_REQUIRED fue eliminada en PHP 8
- Corregido con ?? '' en el primero y quitando el flag
  obsoleto en el segundo
- Modificado: c.cuenta.php


--------------------------------------------------------
  FIX — REDIRECCIONES ROTAS EN INSTALACIONES EN
  SUBDIRECTORIO
--------------------------------------------------------
- redirectTo() armaba las URLs de redireccion (login,
  restricciones de nivel de acceso) sin anteponer
  $this->settings['url']
- Cualquier instalacion que no viva en la raiz del
  dominio (ej. midominio.com/foro/) terminaba
  redirigiendo fuera del subdirectorio, dando 404
- Corregido anteponiendo la URL base del sitio a los
  destinos relativos, sin afectar la proteccion anti-
  open-redirect ya existente
- Modificado: c.core.php


--------------------------------------------------------
  FIX — BOTON "MOVER POSTS" Y "VOLVER AL LISTADO" DEL
  ADMIN ROTOS EN SUBDIRECTORIO
--------------------------------------------------------
- Ambos botones armaban location.href hardcodeado sin el
  prefijo {$tsConfig.url}, a diferencia de los botones
  vecinos en los mismos archivos que si lo usaban
- Corregido anteponiendo {$tsConfig.url} en los dos
- Modificado: m.admin_cats.tpl, m.admin_noticias.tpl


--------------------------------------------------------
  FIX — ICONO RSS DEL HOME ROTO EN SUBDIRECTORIO
--------------------------------------------------------
- Hardcodeaba /rss/ultimos-post sin anteponer la URL
  base del sitio
- Corregido con {$tsConfig.url}
- Modificado: m.home_last_posts.tpl


--------------------------------------------------------
  FIX — LIBRERIA GD MARCADA COMO "N/A" EN CREDITOS DEL
  ADMIN AUNQUE ESTUVIERA ACTIVA
--------------------------------------------------------
- La accion creditos de admin.php nunca asignaba
  tsVersion a la plantilla (bloque vacio), a diferencia
  de otras acciones que si lo hacen
- Corregido agregando la asignacion faltante
- Modificado: admin.php


--------------------------------------------------------
  FIX — PAGINA PUBLICA /RETOS/ INACCESIBLE ("NO DIRECT
  ACCESS." Y ERROR 500)
--------------------------------------------------------
- La guarda de acceso directo comprobaba una constante
  inexistente (TSCRIPT en vez de TS_HEADER, usada en el
  resto del core), por lo que fallaba siempre
- Se llamaba a un metodo inexistente
  ($tsCore->setTitle())
- Faltaba la regla de reescritura en el .htaccess (si
  presente para calendario/misiones/premios)
- Corregidos los tres bugs
- Modificado: retos.php, .htaccess


--------------------------------------------------------
  FIX — CHECKBOXES Y RADIOS QUE NO REFLEJABAN LA
  SELECCION REAL DEL USUARIO (JQUERY .ATTR VS .PROP)
--------------------------------------------------------
- En save_configs() las categorias de interes de /mi/
  nunca se guardaban por leerse con attr('checked') en
  vez de prop('checked')
- Al suspender a un miembro de una comunidad,
  member_admin_submit() leia el mismo atributo sobre dos
  radios con el mismo name, por lo que jQuery siempre
  devolvia el del primero y la suspension nunca se
  aplicaba
- Corregido leyendo el estado real con .prop() y
  :checked respectivamente
- Modificado: portal.js, comunidades.js


--------------------------------------------------------
  FIX — DOBLE ESCAPADO DE HTML EN ADJUNTOS DEL MURO
  (FOTO, ENLACE Y VIDEO)
--------------------------------------------------------
- ajaxCheck($return=true) ya devolvia los valores
  escapados con htmlspecialchars, y streamPost() los
  volvia a escapar antes del INSERT, duplicando
  entidades
- En el titulo de YouTube el problema era triple porque
  get_meta_tags() ya entrega el texto con entidades
  codificadas de origen y tampoco se decodificaba antes
  de la primera pasada
- Corregido devolviendo valores crudos desde ajaxCheck()
  (titulo de YouTube decodificado) y dejando el escapado
  como unico paso, en streamPost()
- Modificado: c.muro.php


--------------------------------------------------------
  FIX — PORTADA DE PERFIL NO SE MOSTRABA EN /MI/
--------------------------------------------------------
- La plantilla usa $tsPerfil.user_fondoper para la
  portada, pero el controlador de /mi/ nunca asignaba
  $tsPerfil a Smarty
- Corregido cargandolo con tsCuenta->loadPerfil(), igual
  que en la edicion de perfil
- Modificado: portal.php


--------------------------------------------------------
  FIX — IMAGEN DE FONDO DE PERFIL NUNCA SE APLICABA
--------------------------------------------------------
- Typo de tipo MIME en la plantilla: type="text/style"
  en vez de text/css
- Por especificacion HTML5 los navegadores ignoran por
  completo un <style> con un type invalido, asi que el
  background-image nunca se veia aunque la URL fuera
  correcta
- Modificado: t.perfil.tpl


--------------------------------------------------------
  FIX — CHECKBOXES DE "ME GUSTARIA" NO SE MOSTRABAN
  MARCADOS EN EL PERFIL
--------------------------------------------------------
- Typo en la plantilla de edicion: pedia
  $tsPData.gustaria / $tsPerfil.p_gustaria en vez de
  .gustos / .p_gustos, el nombre real usado en toda la
  base de datos y el resto del codigo
- Modificado: m.cuenta_perfil_me.tpl


--------------------------------------------------------
  FIX — SELECTS "FUMO" Y "TOMO ALCOHOL" VACIOS EN EL
  PERFIL
--------------------------------------------------------
- La plantilla busca tsPData.fumo y tsPData.alcohol por
  separado, pero en datos.php solo existia el array
  combinado fumo_tomo
- Agregados los arrays fumo y alcohol por separado
- Modificado: datos.php


--------------------------------------------------------
  BBDD — FALTAN PROVINCIAS/ESTADOS DE 75 PAISES EN EL
  FORMULARIO DE PERFIL
--------------------------------------------------------
- Confirmado uno por uno (Republica Dominicana,
  Islandia, Singapur entre otros): el array $estados
  simplemente no los tenia cargados
- Agregados los 75 paises faltantes, con divisiones
  administrativas reales o una entrada generica para
  territorios sin subdivisiones formales
- Modificado: geodata.php


--------------------------------------------------------
  FIX — BUSCADOR GLOBAL SOLO APARECIA EN UN POST
  INDIVIDUAL
--------------------------------------------------------
- El bloque del banner superior alternaba entre el
  buscador global y el banner de ads 468 usando una
  condicion sin relacion ($tsPage == 'posts' &&
  $tsPost.post_id)
- Corregido: ads 468 solo en la home, buscador en el
  resto de paginas
- Modificado: logo.tpl


--------------------------------------------------------
  FIX — BOTONES "OLVIDE MI CONTRASENA" Y "NO ME LLEGO
  EL CORREO DE CONFIRMACION" NO HACIAN NADA
--------------------------------------------------------
- Llamaban a remind_password() y resend_validation() en
  el onclick, pero esas funciones JS no existian en
  ningun archivo
- El backend ya estaba implementado (ajax.recover.php);
  solo faltaba conectarlo
- Implementadas ambas funciones siguiendo el mismo
  patron AJAX que el resto del login
- Modificado: t.login.tpl


--------------------------------------------------------
  FIX — AVATAR: "SUBIR POR URL" RECHAZABA LINKS CON
  ?TOKEN= (CDNS COMO DISCORD/GOOGLE)
--------------------------------------------------------
- La validacion de extension no ignoraba el query string
  de la URL
- Ademas el boton quedaba bloqueado para siempre tras el
  primer intento: el callback complete lo volvia a
  deshabilitar en vez de reactivarlo
- Corregidos ambos bugs
- Modificado: cuenta.js


--------------------------------------------------------
  FIX — VISTA PREVIA DE ADJUNTOS EN EL MURO NO SE
  MOSTRABA TRAS ADJUNTAR
--------------------------------------------------------
- Una regla CSS generica del tema, #attaContent
  div{display:none}, ocultaba cualquier div hijo del
  composer, incluidos los contenedores de la tarjeta de
  previsualizacion (#fotoFrame, #enlaceFrame,
  #videoFrame)
- Solo habia una excepcion parcial para los divs
  internos de video, pero ninguno de los tres
  contenedores tenia una regla que los reactivara a
  ellos mismos
- Corregido reactivandolos explicitamente
- Modificado: perfil.css


--------------------------------------------------------
  FIX — MINIATURA DE YOUTUBE CON BOTON DE PLAY MAL
  POSICIONADO EN LOS POSTS
--------------------------------------------------------
- youtube_lazy_load() ya armaba correctamente el wrapper
  con la miniatura y el boton de play dentro, pero esas
  clases (.video-wrapper, .play-button) no tenian ningun
  CSS asociado
- Sin altura definida la miniatura colapsaba, y sin
  posicionamiento el boton quedaba flotando fuera de
  lugar en vez de centrado
- Agregado el CSS faltante (relacion de aspecto 16:9,
  acorde al width=560 height=315 que ya genera el
  backend, y boton centrado con position:absolute)
- Modificado: estilo.css


------------------------------------------------------
  FIX — WIDGET "ULTIMOS COMENTARIOS" CON ALTURA
        INCONSISTENTE
------------------------------------------------------
- En extras.css habia dos reglas !important sobre
  #ult_comm con los valores invertidos (height:auto y
  min-height:345px en vez de un max-height)
- Esas reglas pisaban cualquier altura fijada desde la
  plantilla
- Sin comentarios, la caja quedaba forzada a un minimo
  de 345px con el mensaje vacio flotando arriba y un
  hueco enorme debajo
- Con muchos comentarios, crecia sin limite y
  descuadraba la barra lateral
- Corregido a max-height:335px con scroll
- Se realineo tambien el icono del estado vacio (estaba
  desalineado a la izquierda por un display:block de
  mas) y se movio el CSS estatico de la plantilla a
  clases dedicadas
- Modificado: extras.css, m.home_last_comments.tpl


------------------------------------------------------
  MEJORA — CKEDITOR YA NO CARGA EN TODO EL SITIO
------------------------------------------------------
- El script de CKEditor (~300 KiB, 59% sin usar fuera
  del editor) se cargaba en el <head> global de cada
  pagina, incluida la home
- Esto bloqueaba el renderizado inicial mas de 4
  segundos en conexiones lentas, segun PageSpeed
  Insights
- Ahora solo se incluye cuando el controlador marca
  explicitamente que la pagina lo necesita (crear/
  editar post, ver post con formulario de comentarios)
- Modificado: main_header.tpl, agregar.php, posts.php


------------------------------------------------------
  MEJORA — BANDERAS DE PAISES MIGRADAS DE PNG A SVG
------------------------------------------------------
- Las 249 banderas ISO 3166-1 pasaron de mapas de bits
  fijos a 16x11px a vectores SVG
- Se ven nitidas en cualquier densidad de pantalla
  (Retina/moviles) y pesan igual o menos que el PNG
  equivalente
- Se completaron 10 codigos que faltaban en el set
  original: aq, bl, bq, cw, gg, im, je, mf, ss, sx
- Modificado: c.ultreg.php, c.tema_autor.tpl,
  m.fotos_ver_content.tpl, m.monitor_listado.tpl,
  head_submenu.tpl, p.live.vcard.tpl,
  p.perfil.follows.tpl, t.usuarios.tpl


------------------------------------------------------
  FIX — BOTON "DAR PUNTOS" DEL POST NO FUNCIONABA BIEN
------------------------------------------------------
- El input de puntos se precargaba con el limite por
  rango del votante, no con los puntos que el usuario
  realmente tenia disponibles
- Al votar sin tocar el numero, casi siempre se mandaba
  una cantidad mayor a la disponible y el backend lo
  rechazaba
- En el JavaScript, el modulo se ocultaba sin importar
  si el voto tuvo exito o fallo
- El bloqueo contra doble envio solo se liberaba en
  errores de red, dejando el boton inutilizable hasta
  recargar la pagina
- Ahora el input respeta el minimo entre ambos limites,
  el modulo solo se oculta cuando el voto tiene exito, y
  el bloqueo se libera correctamente en cualquier tipo
  de error
- Modificado: m.posts_metadata.tpl, funciones.js,
  acciones.js


------------------------------------------------------
  MEJORA — MENSAJE "NADA POR AQUI" + PESTANA SEMANA
           ACTIVA POR DEFECTO
------------------------------------------------------
- En TOP Usuarios y TOP Posts de la home, cuando un
  periodo (Ayer/Semana/Mes/Historico) no tiene datos,
  ahora se muestra un mensaje en vez de dejar el
  listado en blanco
- La pestana marcada al cargar la pagina pasa de Ayer a
  Semana, con mas probabilidad de tener contenido si
  todavia no se publico nada ese dia
- Modificado: m.home_top_users.tpl, m.home_top_posts.tpl


------------------------------------------------------
  FIX — FILTROS DE TOP USUARIOS / TOP POSTS EN LA HOME
        NO ACTUALIZABAN EL CONTENIDO
------------------------------------------------------
- Los cuatro listados por periodo (Ayer/Semana/Mes/
  Historico) estaban duplicados por un {foreach} mal
  anidado, asi que Smarty repetia el bloque completo una
  vez por cada periodo devuelto por el backend
- El boton de filtro se marcaba como activo, pero el
  listado visible no cambiaba porque el JavaScript solo
  encontraba la primera copia duplicada
- Ahora cada listado se genera una sola vez, leyendo
  directamente el periodo correspondiente
- Modificado: m.home_top_users.tpl, m.home_top_posts.tpl


------------------------------------------------------
  FIX — FOTOS Y VIDEOS DEL MURO NO SE MOSTRABAN AL VERLOS
------------------------------------------------------
- Al hacer clic para reproducir un video o ver una foto
  adjunta en una publicacion del muro, el contenido
  desaparecia en vez de mostrarse (la funcion dejaba el
  contenido vacio)
- Ahora el video carga un reproductor embebido real de
  YouTube, y la foto carga la imagen real en su tamano
  original (manteniendo proporcion)
- Corregido tambien un problema de anidacion en el HTML
  del muro: el pie de la publicacion (fecha, Me gusta,
  Comentar, IP) quedaba fuera de la columna de contenido
  en vez de debajo del video/foto
- Modificado: perfil.js, m_perfil_muro_story.tpl


------------------------------------------------------
  NUEVO — ENCUESTAS EN LOS POSTS
------------------------------------------------------
- Al publicar se puede anadir una encuesta opcional con
  varias opciones de respuesta
- La votacion se procesa por AJAX y cada usuario solo
  puede votar una vez por encuesta
- Los resultados se muestran en barras de porcentaje
  dentro del propio post
- Anadidas las tablas p_encuestas, p_encuestas_opciones
  y p_encuestas_votos
- Modificado: c.posts.php, ajax.posts.php,
  m.agregar_form.tpl, m.posts_content.tpl, database.php


------------------------------------------------------
  NUEVO — COLOR DE CATEGORIA EN EL TITULO DE LOS POSTS
------------------------------------------------------
- Cada categoria puede tener su propio color, elegible
  desde Admin -> Categorias con un selector de color
- Se aplica al titulo del post en Ultimos Posts,
  Tendencias y Top Posts
- Anadida la columna c_color a p_categorias
- Modificado: c.admin.php, c.posts.php, c.tops.php,
  m.admin_cats.tpl, m.home_last_posts.tpl,
  m.home_tendencias.tpl, m.home_top_posts.tpl,
  database.php


------------------------------------------------------
  NUEVO — COLOR DE RANGO EN LOS COMENTARIOS
------------------------------------------------------
- El nick de quien comenta se pinta con el color de su
  rango, igual que ya ocurre en otras partes del sitio
- Reutiliza u_rangos.r_color, sin tablas ni columnas
  nuevas
- Modificado: c.posts.php, p.comentario.ajax.tpl


------------------------------------------------------
  NUEVO — COMENTARIO DESTACADO POR VOTOS
------------------------------------------------------
- Los comentarios que superan un numero configurable de
  votos se resaltan con un borde y un icono de estrella
- El umbral se configura desde Admin -> Configuracion
  (0 lo desactiva)
- Anadida la columna c_com_votos_destacado a
  w_configuracion
- Modificado: c.admin.php, m.admin_configs.tpl,
  p.comentario.ajax.tpl, extras.css, database.php


------------------------------------------------------
  NUEVO — AVISO DE POSIBLE CLON AL REGISTRARSE
------------------------------------------------------
- Si la IP de un nuevo registro coincide con la de otro
  miembro o con una sesion activa de otro usuario, se
  avisa automaticamente a todos los admins/moderadores
- Reutiliza u_avisos, sin tablas nuevas
- Modificado: c.registro.php


------------------------------------------------------
  NUEVO — MENSAJE DE ENLACES CAIDOS
------------------------------------------------------
- Desde el panel de denuncias de posts, el moderador
  puede marcar un post con enlaces caidos
- Se avisa al autor para que los reponga y se muestra
  un banner de aviso en la pagina del post mientras
  siga marcado
- Anadida la columna post_link a p_posts
- Modificado: c.moderacion.php, moderacion.php,
  m.posts_content.tpl, extras.css, database.php


------------------------------------------------------
  NUEVO — TOP DE FOTOS
------------------------------------------------------
- Nueva pestana "Fotos" en la seccion Top, junto a
  Posts, Usuarios, Comunidades y Temas
- Muestra el top de fotos por votos positivos,
  visitas y medallas, por periodo de tiempo
- Modificado: c.tops.php, top.php, t.tops.tpl,
  head_submenu.tpl
- Anadido: m.top_fotos.tpl


------------------------------------------------------
  NUEVO — SISTEMA DE REFERIDOS
------------------------------------------------------
- Al registrarse, el formulario acepta opcionalmente
  el nick de quien invito
- Si existe, se suma 1 a su contador de referidos y
  queda visible en su perfil
- Panel nuevo en Admin -> Referidos para ver y
  eliminar relaciones
- Anadida la columna user_referidos a u_miembros y la
  tabla u_referidos
- Modificado: c.registro.php, ajax.registro.php,
  registro.js, t.registro.tpl, c.admin.php, admin.php,
  t.admin.tpl, m.admin_sidemenu.tpl,
  m.perfil_headinfo.tpl, c.cuenta.php, .htaccess,
  database.php
- Anadido: m.admin_referidos.tpl


------------------------------------------------------
  NUEVO — USUARIO VERIFICADO
------------------------------------------------------
- Insignia de verificacion junto al nick, gestionable
  desde Admin -> Usuarios
- Se muestra en el perfil y en el panel de autor de
  los posts
- Anadida la columna user_verificado a u_miembros
- Modificado: c.admin.php, admin.php,
  m.admin_users.tpl, c.cuenta.php,
  m.perfil_headinfo.tpl, c.posts.php,
  m.posts_autor.tpl, database.php


------------------------------------------------------
  NUEVO — PUNTOS AL ACEPTAR DENUNCIA
------------------------------------------------------
- Si denuncias un post, mensaje, foto o usuario y la
  denuncia se acepta (se borra el contenido o se
  suspende al usuario), recibes puntos configurables
- Configurable desde Admin -> Configuracion (0 lo
  desactiva)
- Anadidas las columnas d_posts, d_user, d_mensajes
  y d_fotos a w_configuracion
- Modificado: c.admin.php, c.moderacion.php,
  m.admin_configs.tpl, database.php


------------------------------------------------------
  NUEVO — CATEGORIAS SOLO PARA STAFF
------------------------------------------------------
- Una categoria puede marcarse como exclusiva, visible
  y publicable solo para los rangos con el nuevo
  permiso "Ver categorias de staff"
- Se oculta en los selectores, en los listados y al
  intentar acceder directamente al post
- Anadida la columna c_staff a p_categorias
- Modificado: database.php, c.core.php, c.admin.php,
  c.posts.php, m.admin_rangos.tpl, m.admin_cats.tpl,
  head_categorias.tpl, m.agregar_form.tpl,
  m.posts_content.tpl


------------------------------------------------------
  NUEVO — GIFS SIN PRECARGA, CON PLAY/PAUSA
------------------------------------------------------
- Los gifs animados en posts y comentarios ya no se
  descargan enteros de entrada
- Se muestra una miniatura estatica (primer frame)
  generada y cacheada en el servidor, con un boton de
  play encima
- El gif animado real solo se descarga si el usuario
  pulsa play
- Anadido: gif-thumb.php, modifier.gif_lazy.php
- Modificado: main_header.tpl, extras.css,
  m.posts_content.tpl, p.comentario.ajax.tpl


------------------------------------------------------
  NUEVO — LIMPIEZA AUTOMATICA DE VISITAS Y SESIONES
------------------------------------------------------
- Dos tareas nuevas en el cron de mantenimiento:
  borran las visitas (w_visitas) y sesiones inactivas
  (u_sessions) de mas de 24h
- Ejecutan OPTIMIZE TABLE despues de borrar, para
  liberar el espacio en disco de verdad (un DELETE
  por si solo no reduce el tamano del archivo en
  InnoDB)
- Se arreglo que los ajustes del cron (clave y tareas
  activas) no se guardaban al pulsar Guardar en
  Configuracion
- Anadidas las columnas cron_clean_visitas y
  cron_clean_sessions a w_configuracion
- Modificado: cron.php, c.admin.php,
  m.admin_configs.tpl, database.php


------------------------------------------------------
  NUEVO — PREGUNTAS FRECUENTES ADMINISTRABLES
------------------------------------------------------
- Nueva pagina publica en /pages/preguntasfrecuentes/
  con acordeon
- Panel completo en Admin -> Preguntas Frecuentes para
  crear, editar, ordenar y ocultar/mostrar cada
  pregunta sin tocar codigo
- Anadida la tabla p_preguntas
- Modificado: c.admin.php, c.core.php, admin.php,
  t.admin.tpl, m.admin_sidemenu.tpl, t.pages.tpl,
  main_footer.tpl, extras.css, .htaccess, database.php
- Anadido: m.admin_preguntas.tpl,
  m.pages_preguntasfrecuentes.tpl


------------------------------------------------------
  MEJORA — AVISO SMTP NO CONFIGURADO
------------------------------------------------------
- En Admin -> Configuracion -> SMTP, si el servidor
  SMTP no esta configurado aparece un aviso amarillo
  indicando que los emails no funcionaran correctamente
  (la mayoria de hostings bloquean mail())
- Desaparece automaticamente al rellenar el campo
  de servidor SMTP
- Modificado: m.admin_configs.tpl


------------------------------------------------------
  NUEVO — SOPORTE PHPMAILER 7.x
------------------------------------------------------
- Se incluye PHPMailer 7.1.1 en inc/ext/phpmailer/
  (PHPMailer.php, SMTP.php, Exception.php)
- Los emails de registro, recuperacion de contrasena
  y bienvenida se envian via SMTP de forma fiable
  desde cualquier hosting
- Si no hay SMTP configurado cae al mail() de PHP
  como fallback
- Anadido: inc/ext/phpmailer/


------------------------------------------------------
  NUEVO — REPOSITORIO DE COMPLEMENTOS CON INSTALACION
  AUTOMATICA
------------------------------------------------------
- Nueva seccion Admin -> Complementos (/admin/plugins)
  que descarga un catalogo remoto (catalog.json alojado
  en phpost.es) y permite instalar, actualizar o
  desinstalar plugins en un clic
- Cada paquete es un .zip con plugin.json, migraciones
  SQL opcionales y sus propios archivos
- Verificacion por checksum SHA-256 antes de extraer
  el paquete
- Extraccion en carpeta aislada (inc/plugins/{slug}/),
  con proteccion contra rutas que intenten escribir
  fuera de esa carpeta
- La descarga de paquetes solo se permite desde
  phpost.es por seguridad
- Anadida la tabla w_plugins y la columna
  plugins_catalog_url en w_configuracion
- Anadido: c.plugins.php, m.admin_plugins.tpl
- Modificado: admin.php, .htaccess, t.admin.tpl,
  m.admin_sidemenu.tpl, database.php


------------------------------------------------------
  CORRECCION — RUTA /admin/feed ROTA
------------------------------------------------------
- La regla de .htaccess pasaba act=feed pero el
  controlador comprobaba $action, asi que el chequeo
  de nueva version del panel de administracion nunca
  se ejecutaba y siempre caia en el dashboard por
  defecto
- Corregido a action=feed
- Modificado: .htaccess


------------------------------------------------------
  MEJORA — REPOSITORIO DE COMPLEMENTOS: INSTALACION EN
  RUTAS REALES, CON BARANDILLAS DE SEGURIDAD
------------------------------------------------------
- El instalador de plugins ya no se limita a copiar
  archivos dentro de una carpeta aislada: ahora puede
  escribir en rutas reales del sitio (inc/class/,
  inc/php/, inc/ext/, themes/*/templates/,
  themes/*/css/, themes/*/js/) para poder instalar mods
  reales como Tienda, Retos o Chat tal cual ya se
  empaquetan, sin tener que reestructurarlos
- Antes de tocar un solo archivo se valida el paquete
  entero: cualquier ruta fuera de esas carpetas, o que
  intente sobrescribir un archivo nucleo (index.php,
  c.core.php, admin.php, .htaccess...), cancela TODA la
  instalacion indicando el archivo exacto y el motivo
- Todo archivo que ya exista se respalda automaticamente
  en inc/plugins/_backups/{slug}/{fecha}/ antes de
  sobrescribirse
- Nueva pantalla de confirmacion previa que lista cada
  archivo (nuevo, sobrescribe o bloqueado) antes de
  ejecutar nada
- Anadidas las columnas modo y archivos a w_plugins
- Modificado: c.plugins.php, admin.php,
  m.admin_plugins.tpl, database.php


------------------------------------------------------
  NUEVO — NOTAS DE STAFF
------------------------------------------------------
- Nueva seccion Admin -> Notas de Staff (/admin/notas)
  para que administradores y moderadores dejen avisos
  y comentarios internos entre ellos, invisible para
  usuarios normales
- Cada nota muestra autor y fecha; el propio autor o
  un administrador puede borrarla
- Empaquetado como complemento instalable desde el
  repositorio (modo raiz), con 3 pasos manuales tras
  instalar (edicion de admin.php, t.admin.tpl y
  m.admin_sidemenu.tpl, archivos nucleo que el
  instalador no toca por seguridad)
- Anadida la tabla w_notas_staff
- Anadido: c.notas_staff.php, m.admin_notas.tpl


------------------------------------------------------
  NUEVO — LOS POSTS EDITADOS VUELVEN A APARECER ARRIBA
  DEL HOME
------------------------------------------------------
- Al editar un post, si pasaron 5 o mas dias desde la
  ultima vez que subio (o desde que se creo, si nunca
  subio), vuelve a aparecer entre los mas recientes
  del home
- Editar el mismo post varias veces el mismo dia no
  hace nada mas, para evitar que se abuse manteniendo
  un post siempre destacado
- No se toca la fecha real de publicacion (post_date),
  se compara siempre en la misma unidad (timestamp)
  para que un post recien publicado nunca quede por
  debajo de uno viejo con "subida"
- Anadida la columna post_bump_fecha a p_posts
- Modificado: c.posts.php


------------------------------------------------------
  NUEVO — POSTS CONJUNTOS (COAUTORIA ENTRE DOS
  USUARIOS)
------------------------------------------------------
- Al crear o editar un post ahora se puede invitar a
  otro usuario como coautor, con buscador de nombre y
  autocompletado en vivo (nada de escribir el nick a
  mano y arriesgarse a perder informacion si te
  equivocas de mayusculas, como en versiones antiguas
  de este mod)
- La invitacion queda pendiente hasta que el invitado
  la acepta o la rechaza desde su propia pagina de
  autor del post; nadie queda como coautor sin haberlo
  confirmado
- Los puntos que reciba el post se reparten al 50%
  entre autor y coautor (el autor principal se queda
  el resto si es impar)
- El coautor puede salir de la coautoria cuando quiera
- Anadidas las columnas post_usec y post_usec_status
  a p_posts
- Anadidas las funciones buscarCoautor(),
  coautorResponder() y coautorSalir()
- Nuevas acciones AJAX posts-buscar-coautor,
  posts-coautor-responder y posts-coautor-salir
- Modificado: c.posts.php, ajax.posts.php,
  m.agregar_form.tpl, m.posts_autor.tpl


------------------------------------------------------
  CORRECCION — BORRAR UN POST NO DESCONTABA LOS PUNTOS
  QUE HABIA GANADO
------------------------------------------------------
- Al eliminar un post, se quitaba del contador de posts
  del autor pero user_puntos se quedaba igual, dejando
  puntos "gratis" de un post que ya no existia (y
  permitiendo repetir el ciclo recibir puntos -> borrar
  -> repetir)
- Ahora se restan al borrar, con GREATEST(0, ...) para
  que nunca quede en negativo
- Si el post tenia coautor aceptado, se le resta
  tambien su parte proporcional
- De paso se corrigio otro bug relacionado: cuando un
  moderador borraba el post de OTRA persona (no el
  suyo propio), la consulta filtraba por dueno y nunca
  encontraba los datos del post, asi que el contador de
  posts del autor real tampoco se actualizaba en ese
  caso
- Modificado: c.posts.php


------------------------------------------------------
  NUEVO — LOG DE MODERACION DE USUARIO
------------------------------------------------------
- Nuevo boton "Ver log" en el panel de moderacion
  (denuncias de usuarios y listado de suspendidos)
- Abre un historial tipo chat con todo lo relevante
  sobre un usuario: notas internas del staff y
  acciones automaticas (suspensiones, reactivaciones,
  avisos enviados y contenido eliminado), cada una con
  fecha, autor y motivo
- El staff puede anadir notas manuales directamente
  desde el mismo panel, sin salir de la denuncia o el
  listado
- Sugerido por un usuario del foro de phpost.es
- Anadida la tabla w_userlog
- Anadidas las funciones addUserLog(), getUserLog() y
  addUserLogNota() en c.moderacion.php, con enganches
  automaticos en banUser(), rebootUser(), deletePost(),
  deleteFoto() y deleteMps()
- Nuevas acciones AJAX userlog y userlog_nota en
  ajax.moderacion.php
- Anadido: p.moderacion_userlog.tpl
- Modificado: m.mod_report_users.tpl,
  m.mod_ban_users.tpl, moderacion.js, extras.css,
  database.php


------------------------------------------------------
  NUEVO — AUTOGUARDADO DE BORRADORES
------------------------------------------------------
- Al escribir un post (nuevo o edicion), el contenido
  se guarda solo cada 25 segundos sin tocar nada, ademas
  del boton manual "Guardar borrador" que ya existia
- Nuevo boton "Mis Borradores" para ver, continuar o
  eliminar los borradores guardados, con soporte para
  varios a la vez
- Sugerido por un usuario del foro de phpost.es
- Anadida la columna b_links a p_borradores
- Anadidas las funciones addBorrador(), updateBorrador(),
  deleteBorrador() y getBorradores() en c.posts.php
- Nuevas acciones AJAX borradores-agregar,
  borradores-guardar, borradores-borrar y
  borradores-listar en el nuevo ajax.borradores.php
- Anadido: p.borradores.listar.tpl
- Modificado: m.agregar_form.tpl,
  m.agregar_javascript.tpl, extras.css, database.php


------------------------------------------------------
  NUEVO — SUGERENCIAS DE LA COMUNIDAD
------------------------------------------------------
- Nueva seccion /sugerencias/ donde cualquier usuario
  logueado puede proponer ideas para mejorar el sitio
  y votarlas (1 voto por usuario, sin descontar)
- Mientras se escribe el titulo, se buscan sugerencias
  parecidas ya publicadas antes de dejar publicar, para
  evitar duplicados
- Listado con pestanas por estado y contador: Todas,
  Nueva, En revision, Aceptada, Rechazada,
  Implementada, ordenado por votos
- El staff puede responder oficialmente a cada
  sugerencia y cambiarle el estado desde el mismo
  detalle, sin pasar por el panel de administracion
- Nuevo icono en el menu principal (junto a Calendario
  y Chat) para acceder a Sugerencias
- Anadidas las tablas w_sugerencias y
  w_sugerencias_votos
- Anadida la clase c.sugerencias.php con getSugerencias(),
  getSugerencia(), addSugerencia(), votar(), responder(),
  cambiarEstado() y contarPorEstado()
- Nuevo controlador de pagina sugerencias.php (listado
  y detalle)
- Nuevo controlador AJAX ajax.sugerencias.php con las
  acciones sugerencias-agregar, sugerencias-votar,
  sugerencias-responder, sugerencias-estado y
  sugerencias-buscar
- Anadido: sugerencias.tpl, m.sugerencias_listado.tpl,
  p.sugerencias.detalle.tpl
- Modificado: head_menu.tpl, database.php


------------------------------------------------------
  FIX — SMILIES SIN MOSTRAR EN PERFIL, MURO Y MI
------------------------------------------------------
- Al publicar un estado nuevo en el muro, comentar en
  el muro de alguien, o ver el feed de gente que
  seguis (seccion "Mi"), los codigos de emoticones se
  mantenian como texto plano en vez de convertirse en
  la imagen del smiley
- Ya funcionaba correctamente al ver el muro de un
  perfil y en los comentarios
- A partir de un reporte del foro de phpost.es (fix
  original para Risus 1.3, verificado y aplicado sobre
  el codigo actual de Risus Nova)
- Modificado: c.muro.php (streamPost(),
  streamRepost(), getNews())


------------------------------------------------------
  NUEVO — CATALOGO DE VENTAS
------------------------------------------------------
- Nuevo marketplace interno de la comunidad en
  /catalogo/
- Cualquier usuario logueado puede publicar un
  producto en venta (titulo, descripcion, precio,
  categoria, condicion, imagen principal + hasta 3
  extra)
- Contacto al vendedor por mensaje privado real desde
  el mismo detalle del producto
- Desde "Mis publicaciones" el vendedor cambia el
  estado (Disponible/Reservado/Vendido/Pausado),
  destaca su publicacion gastando puntos propios (7
  dias arriba del listado, costo configurable), o la
  elimina
- Categorias administrables por el staff directamente
  desde la pagina del catalogo
- Nuevo icono en el menu principal (solo visible si el
  catalogo esta activado) y nueva seccion en Admin ->
  Configuracion
- Reescrito de cero a partir de un complemento del
  foro de phpost.es (Risus 1.3, MyISAM), no portado
  tal cual: InnoDB, permisos modernos, subida de
  imagenes real (c.upload.php, compatible con
  Cloudinary) y contacto real (c.mensajes.php) en vez
  de reinventarlos
- Anadidas las tablas w_catalogo, w_catalogo_categorias
  y w_catalogo_imagenes
- Anadida la clase c.catalogo.php con getCategorias(),
  getListado(), getMiCatalogo(), getProducto(),
  publicar(), cambiarEstado(), eliminar(), destacar(),
  contactarVendedor(), addCategoria() y
  eliminarCategoria()
- Nuevo controlador de pagina catalogo.php (listado,
  detalle y mis publicaciones)
- Nuevo controlador AJAX ajax.catalogo.php con las
  acciones catalogo-publicar, catalogo-estado,
  catalogo-eliminar, catalogo-destacar,
  catalogo-contactar, catalogo-cat-agregar y
  catalogo-cat-borrar
- Anadido: catalogo.tpl, m.catalogo_listado.tpl,
  m.catalogo_mi.tpl, p.catalogo.detalle.tpl
- Modificado: head_menu.tpl, m.admin_configs.tpl,
  database.php


------------------------------------------------------
  MEJORA — MENU PRINCIPAL MAS ORDENADO
------------------------------------------------------
- El acceso a Staff ahora es solo un icono (escudo),
  sin el texto "Staff", igual que Calendario, Chat,
  Sugerencias y Catalogo
- El menu de usuario (Favoritos, Mis Borradores, Mi
  Cuenta) se agrupo en un desplegable bajo el nombre
  de usuario, para que no se amontonen tantos iconos
  sueltos arriba cuando hay muchas secciones activadas
- El desplegable es CSS puro (sin JavaScript), se abre
  al pasar el mouse sobre el nombre de usuario
- Modificado: head_menu.tpl


------------------------------------------------------
  MEJORA — INFORMACION DE LA SUSPENSION AL LOGUEARSE
------------------------------------------------------
- Antes, un usuario banneado podia completar el login
  con normalidad y solo se enteraba de la suspension en
  la siguiente carga de pagina. Ahora se le avisa en el
  momento, en el propio formulario de login
- El aviso indica si es indefinida, permanente o hasta
  que fecha, la razon de la suspension, y que moderador
  la aplico
- Reutiliza getUserBanned() y setSecure() ya existentes,
  sin columnas ni tablas nuevas
- Modificado: c.user.php


------------------------------------------------------
  NUEVO — POSTS LIMITADOS PARA VISITANTES
------------------------------------------------------
- Los visitantes sin cuenta solo pueden ver un numero
  limitado de posts al dia; al superarlo, se les pide
  registrarse (reutilizando el mismo estado que ya usan
  los posts privados, sin tocar ninguna plantilla nueva)
- El contador es por IP y se reinicia cada 24h, asi
  nadie queda bloqueado para siempre
- Desactivado por defecto, y la cantidad de posts
  gratis es configurable desde Admin -> Configuracion
- Anadida la tabla w_visitas_limite y las columnas
  c_limite_visitante_activo y
  c_limite_visitante_cantidad a w_configuracion
- Modificado: c.posts.php, c.admin.php,
  m.admin_configs.tpl, database.php


------------------------------------------------------
  NUEVO — AVATAR GIF
------------------------------------------------------
- Los rangos con permiso pueden pegar la URL de un GIF
  externo (Imgur, Tenor...) como avatar, en vez de subir
  un archivo. Al no procesarse en el servidor, la
  animacion nunca se pierde (a diferencia de redimensionar
  un GIF con GD, que solo conserva el primer fotograma)
- Configurable por rango en Admin -> Rangos, con un
  interruptor para que el propio usuario lo active o
  desactive, y un bloqueo permanente que solo puede
  levantar un admin
- Activado de fabrica para todos los rangos en
  instalaciones nuevas
- Anadidas las columnas user_avatar_gif,
  user_avatar_gif_activo y user_avatar_gif_bloqueado
  a u_perfil
- Modificado: c.core.php, c.cuenta.php, c.admin.php,
  m.cuenta_cuenta.tpl, m.admin_rangos.tpl, database.php


------------------------------------------------------
  NUEVO — LOGIN CON DISCORD
------------------------------------------------------
- Los usuarios ya pueden iniciar sesion con su cuenta
  de Discord, igual que con Google (mismo flujo OAuth
  2.0): busca por ID de Discord, luego por email, y si
  no existe crea la cuenta automaticamente
- El admin solo tiene que crear una app gratuita en el
  Portal de Desarrolladores de Discord y pegar el
  Client ID/Secret en Admin -> Configuracion; si se
  dejan vacios, el boton no aparece
- Anadida la columna user_discord_id a u_miembros y
  discord_client_id/discord_client_secret a
  w_configuracion
- Anadido: discord_auth.php
- Modificado: c.admin.php, m.admin_configs.tpl,
  database.php


------------------------------------------------------
  NUEVO — USUARIO DE LA SEMANA EN LA HOME
------------------------------------------------------
- Cada semana se calcula solo, sin que el admin tenga
  que elegir a nadie a mano: puntua a cada usuario por
  los puntos y comentarios recibidos en sus posts, los
  comentarios que escribe y los seguidores nuevos que
  consigue, y destaca al que mas suma
- Se muestra en la home con avatar, rango y sus stats
  de la semana (posts, comentarios, seguidores)
- Administradores y moderadores no entran en el calculo
- Se puede activar/desactivar desde Admin -> Configuracion
- El historial de ganadores se guarda en w_user_semana,
  por si se quiere mostrar mas adelante
- Anadida tabla w_user_semana y columnas c_usersem,
  cron_user_semana a w_configuracion
- Modificado: c.user.php, c.admin.php,
  m.admin_configs.tpl, header.php, t.home.tpl
- Anadido: m.home_usersem.tpl, database.php


------------------------------------------------------
  NUEVO — FUENTE DEL CONTENIDO AL PUBLICAR POST
------------------------------------------------------
- Al crear o editar un post se puede indicar la URL de
  origen del contenido, o marcar la casilla "Contenido
  Propio" si la informacion es de autoria propia
- La fuente se muestra en la pagina del post junto a
  Categoria y Fecha: como enlace "Ver original" o como
  "Contenido propio"
- Anadida la columna post_fuente a p_posts
- Modificado: c.posts.php, m.agregar_form.tpl,
  m.posts_metadata.tpl, database.php


------------------------------------------------------
  NUEVO — CHAT EN TIEMPO REAL
------------------------------------------------------
- Nueva seccion /chat/ con mensajes en directo y lista
  de usuarios conectados
- Borrado de mensajes por moderadores y limpieza
  automatica de mensajes antiguos
- Anadido: c.chat.php, chat.php, ajax.chat.php,
  t.chat.tpl, tablas w_chat y w_chat_presencia
- Modificado: index.php, .htaccess, head_menu.tpl


------------------------------------------------------
  NUEVO — IMAGEN DE PORTADA EN POSTS
------------------------------------------------------
- Se puede subir una imagen o pegar una URL al crear o
  editar un post, con vista previa en vivo
- Se muestra como banner en la pagina del post y como
  miniatura en los listados de "Ultimos Posts"
- Validacion real de que la URL/archivo es una imagen;
  la carpeta de subida se crea sola si no existe
- Modificado: c.posts.php, m.agregar_form.tpl,
  m.home_last_posts.tpl, m.posts_content.tpl,
  database.php, extras.css


------------------------------------------------------
  MEJORA — CATEGORIAS REDUCIDAS DE 33 A 19
------------------------------------------------------
- Consolidadas varias categorias que se solapaban
  (peliculas/series/anime, tecnologia, apuntes/ebooks/
  ciencia, arte/imagenes, humor/off topic, estilo de
  vida) en categorias mas amplias, para evitar la
  sensacion de comunidad vacia con demasiadas secciones
  sin actividad
- Modificado: database.php, head_categorias.tpl,
  m.admin_cats.tpl, m.home_last_posts.tpl,
  m.home_last_posts_cat.tpl, m.home_tendencias.tpl,
  m.home_topcat.tpl, m.top_posts.tpl,
  m.agregar_form.tpl, t.novatos.tpl


------------------------------------------------------
  MEJORA — VISITAS EN LOS LISTADOS DE POSTS
------------------------------------------------------
- Anadido el numero de visitas de cada post en
  "Ultimos Posts" y en "Tendencias", junto a Puntos y
  Comentarios
- El icono de categoria ahora muestra el nombre al
  pasar el raton por encima, en vez de mostrarlo como
  texto aparte
- Modificado: c.posts.php, m.home_last_posts.tpl,
  m.home_tendencias.tpl


------------------------------------------------------
  MEJORA — BUSCADOR REDISENADO
------------------------------------------------------
- Nueva interfaz en dos columnas: resultados a la
  izquierda y filtros (tipo de busqueda, autor) en un
  sidebar a la derecha, con un estilo mas limpio y
  acorde al resto del sitio
- Simplificado el JavaScript del selector de motor de
  busqueda y eliminado codigo que ya no se usaba
- Todo en una sola plantilla, sin sub-includes
  adicionales
- Modificado: t.buscador.tpl, extras.css


------------------------------------------------------
  NUEVO — PROTECCION CONTRA ROBO DE SESION
------------------------------------------------------
- Anadida una capa extra de seguridad: si tu sesion ya
  iniciada se usa de repente desde un navegador o
  dispositivo distinto al que la creo (senal de que la
  cookie de sesion fue robada y se esta usando desde
  otro sitio), se cierra automaticamente y se te avisa
  por email para que puedas actuar
- Complementa al 2FA, que protege sobre todo frente a
  contrasenas filtradas o adivinadas
- Modificado: c.user.php (clase tsSession),
  database.php



------------------------------------------------------
  CORRECCION — DIV SIN CERRAR EN PUBLICACIONES DEL MURO
------------------------------------------------------
- A cada publicacion del muro (estado, foto, enlace
  o video) le faltaba un </div> de cierre antes del
  bloque de pie de publicacion (Story_Foot)
- Al repetirse en cada publicacion, el desajuste se
  acumulaba y descolocaba el resto de la pagina: el
  pie de pagina (copyright) subia y se mezclaba con
  el contenido, y el formulario de Enlace/Video del
  composer se veia deformado, sin mostrar nada
- Corregido anadiendo el </div> que faltaba
- Modificado: m.perfil_muro_story.tpl

------------------------------------------------------
  CORRECCION — ERROR AL CARGAR EL MURO SIN ADJUNTOS
------------------------------------------------------
- Si una publicacion de tipo foto/enlace/video no
  tenia fila en u_muro_adjuntos, array_merge() recibia
  null como segundo argumento y rompia con error fatal
  en PHP 8, tumbando el muro completo (home, perfil,
  moderacion...)
- Corregido en getNews(), getWall() y getStory():
  ahora se usa un array vacio si no hay adjunto
- Modificado: c.muro.php

------------------------------------------------------
  CORRECCION — VALIDACION AL PUBLICAR ENLACE O VIDEO
------------------------------------------------------
- En streamPost(), el caso 'foto' si comprobaba si
  ajaxCheck() habia devuelto un error antes de usar
  el resultado como array, pero a los casos 'enlace'
  y 'video' les faltaba esa misma validacion
- Si ajaxCheck() fallaba, el codigo seguia intentando
  leer campos de un texto de error como si fuera un
  array, provocando un error fatal y dejando el boton
  Compartir colgado sin publicar nada
- Corregido anadiendo la misma validacion que ya
  tenia el caso 'foto'
- Modificado: c.muro.php

------------------------------------------------------
  CORRECCION — PAGINA DE AVISOS (/monitor/) DABA ERROR 500
------------------------------------------------------
- En getNotificaciones(), un error de tipeo guardaba
  el resultado de la consulta en $query pero intentaba
  leerlo desde una variable distinta ($cuery), lo que
  provocaba un error fatal y dejaba la pagina en blanco
- Corregido el nombre de la variable
- Modificado: c.monitor.php

------------------------------------------------------
  NUEVO — WIDGET "ULTIMOS REGISTRADOS" EN LA HOME
------------------------------------------------------
- Muestra en la barra lateral de la home los ultimos
  usuarios registrados, con su avatar y la bandera de
  su pais (enlazada al filtro de /usuarios/?pais=XX)
- Limite por defecto: 10 usuarios
- Archivos nuevos: c.ultreg.php,
  m.home_ultimos_registrados.tpl
- Modificado: posts.php, t.home.tpl, extras.css

------------------------------------------------------
  CORRECCION — BOTON DE ELIMINAR PUBLICACION ("x") NO HACIA NADA
------------------------------------------------------
- Una regla en extras.css ocultaba #mydialog de forma
  permanente con display:none!important, asi que ningun
  dialogo de confirmacion del sitio (eliminar publicacion,
  subir avatar, etc.) podia llegar a mostrarse, sin dar
  ningun error en consola
- Corregido ocultandolo solo mientras esta vacio
  (#mydialog:empty) y eliminada una caja visual duplicada
  que quedaba flotando detras del dialogo real
- Modificado: extras.css

------------------------------------------------------
  NUEVO — MENCIONES @USUARIO CON NOTIFICACION
------------------------------------------------------
- Al mencionar a alguien con @usuario en un post, un
  comentario, el muro o un comentario del muro, ahora
  le llega un aviso real al Monitor con enlace al
  contenido
- De paso se corrigio una mencion que ya existia a
  medias en comentarios de posts y que avisaba con el
  texto equivocado ("respondio tu comentario" en vez
  de "te menciono")
- Modificado: c.monitor.php, c.posts.php, c.muro.php

------------------------------------------------------
  CORRECCION — FUNCION print_editor() DUPLICADA
------------------------------------------------------
- acciones.js y funciones.js definian exactamente la
  misma funcion para inicializar CKEditor; al cargarse
  funciones.js despues, siempre sobrescribia a la de
  acciones.js
- Por esto, la configuracion simpleUpload del editor de
  posts (subida de imagenes) nunca llegaba a aplicarse
  de verdad, aunque estaba escrita en el codigo
- Corregido anadiendo esa configuracion a la version
  real (funciones.js) y eliminada la copia muerta de
  acciones.js
- Modificado: acciones.js, funciones.js

------------------------------------------------------
  NUEVO — VERIFICACION EN DOS PASOS (2FA)
------------------------------------------------------
- Capa extra de seguridad al iniciar sesion, compatible
  con Google Authenticator, Authy o Microsoft
  Authenticator
- Se activa desde Mi Cuenta -> Seguridad escaneando un
  codigo QR; a partir de ahi cada inicio de sesion pide
  tambien ese codigo de 6 digitos, asi que una
  contrasena robada ya no basta para entrar
- Opcional, se puede desactivar confirmando la
  contrasena actual
- Nuevos: c.totp.php, ajax.2fa.php,
  m.cuenta_seguridad.tpl
- Modificado: c.user.php, ajax.login.php, t.login.tpl,
  database.php







------------------------------------------------------
  NUEVO — MÓDULOS DE LIMPIEZA Y MANTENIMIENTO
------------------------------------------------------
Permite que tareas de limpieza y mantenimiento se
ejecuten automáticamente cada cierto tiempo (cada
hora, por ejemplo) sin que el admin tenga que entrar
manualmente al panel. Tú decides qué tareas activar.

TAREAS DISPONIBLES (todas opcionales, una por una):
- Limpiar intentos de login antiguos (+7 días)
- Marcar retos expirados como cerrados
- Limpiar histórico antiguo de misiones (+90 días)
- Borrar avisos y notificaciones antiguos (+90 días)
- Comprobar invitaciones caducadas

NOTA SOBRE MISIONES: las misiones diarias/semanales
NO necesitan "resetearse" — cada día/semana se crea
automáticamente una fila nueva en u_misiones al
registrar actividad. Esta tarea solo limpia el
histórico viejo para que la tabla no crezca sin
límite con el paso de los años.

NOTA SOBRE RETOS: esta tarea SOLO marca el reto como
cerrado (reto_cerrado=1) cuando su fecha de fin ya
pasó. NO reparte puntos a los ganadores ni calcula
quién quedó 1º/2º/3º — eso debes hacerlo manualmente
desde Admin -> Retos, porque calcular el ranking final
depende del tipo de métrica de cada reto y de tablas
que no están centralizadas en una sola clase.


CONFIGURAR EN EL PANEL:

   Ve a Admin → Configuración → busca
   "Mantenimiento Automático (Cron Jobs)"
   - Pulsa "Generar clave" para crear una clave
     secreta aleatoria (o escribe la tuya)
   - Marca las tareas que quieras activar
   - Guarda

CONFIGURAR EL CRON EN TU HOSTING (cPanel):

   Ve a cPanel → Cron Jobs → Añadir nuevo cron job
   - Frecuencia recomendada: cada hora
     (Minuto: 0, Hora: *, resto: *)
   - Comando:
     wget -q -O /dev/null "https://tudominio.com/cron.php?key=TU_CLAVE_GENERADA"

   Si tu hosting no permite wget, usa curl:
     curl -s "https://tudominio.com/cron.php?key=TU_CLAVE_GENERADA" > /dev/null

   BanaHosting suele dar esta opción en cPanel →
   Avanzado → Trabajos Cron.

PROBAR MANUALMENTE (opcional):

   Visita en tu navegador:
   https://tudominio.com/cron.php?key=TU_CLAVE
   Deberías ver un listado en texto plano de las
   tareas ejecutadas.


------------------------------------------------------
  NUEVO — PREVISUALIZACION DE ENLACES (LINK PREVIEW)
------------------------------------------------------
- Al pegar un enlace en el muro o en el editor de
  Agregar Post, se genera automaticamente una tarjeta
  visual con titulo, descripcion e imagen destacada
  del sitio web — estilo Twitter/X o Discord
- Usa las etiquetas Open Graph (og:title, og:description,
  og:image) de la pagina enlazada
- Si la pagina no tiene Open Graph, hace fallback al
  <title> normal de la pagina, sin imagen
- MURO: el boton "Enlace" ya genera la tarjeta completa
  automaticamente, sin configuracion adicional
- EDITOR DE POSTS: al pegar una URL sola en su propio
  parrafo (pegar + Enter), se convierte en tarjeta
  visual automaticamente, igual que Discord o Notion
- Si la URL esta en medio de una frase no se convierte
  en tarjeta, solo cuando esta sola en su parrafo
- Si la pagina no responde o tarda demasiado, no se
  rompe nada — el enlace queda como texto plano
- Funcion centralizada getLinkPreview() en el core,
  reutilizable para futuras funciones
- Nuevo endpoint AJAX /ajax/link-preview/ para el editor
- Columna: a_image en u_muro_adjuntos (la columna
  a_desc ya existia previamente en la tabla)
- Archivos nuevos: ajax.link-preview.php, link-preview.js
- Modificados: c.core.php, c.muro.php,
  m.perfil_muro_story.tpl, t.agregar.tpl, acciones.js,
  funciones.js, .htaccess

------------------------------------------------------
  NUEVO — ALMACENAMIENTO DE IMÁGENES EN LA NUBE
------------------------------------------------------
Soporte para Cloudinary como almacenamiento de avatares, 
fotos e imágenes de posts, activable/desactivable desde 
Admin → Configuración. Útil para hostings con poco espacio 
en disco. Si Cloudinary falla, cae automáticamente a 
almacenamiento local sin interrumpir el registro.

------------------------------------------------------
  NUEVO — SISTEMA DE INVITACIONES
------------------------------------------------------
- Registro opcional por codigo de invitacion, al
  estilo Forocoches
- Interruptor en Admin -> Configuracion para activar
  o desactivar el modo invitacion en cualquier
  momento, sin perder los codigos generados
- Si esta activo, el formulario de registro muestra
  un campo destacado para introducir el codigo,
  obligatorio para completar el registro
- Codigo invalido, ya usado o caducado bloquea
  el registro con mensaje claro
- Panel Admin -> Invitaciones para generar lotes
  de codigos (1 a 50 de una vez)
- Caducidad opcional por dias configurable al
  generar (0 = sin caducidad)
- Estadisticas rapidas: total generadas, usadas
  y disponibles
- Tabla con todos los codigos, quien los creo,
  estado (disponible / usado / caducado) y quien
  los uso para registrarse
- Trazabilidad completa de quien invito a quien
- Los codigos no usados se pueden eliminar desde
  el panel; los ya usados quedan protegidos
- Tabla: w_invitaciones
- Columna: inv_activo en w_configuracion
- Archivos nuevos: c.invitaciones.php,
  m.admin_invitaciones.tpl
- Modificados: c.registro.php, p.registro.form.tpl,
  m.admin_configs.tpl, admin.php, t.admin.tpl,
  m.admin_sidemenu.tpl, .htaccess

------------------------------------------------------
  NUEVO — SISTEMA DE CALENDARIO DE EVENTOS
------------------------------------------------------
- Pagina publica /calendario/ con calendario visual
  mensual navegable (mes anterior / siguiente / hoy)
- 5 tipos de evento con color personalizable:
  Torneo, Streaming, Reto, Mantenimiento y Otro
- Los eventos se muestran directamente en los dias
  del calendario con el color del tipo
- Modal con detalle del evento al hacer clic:
  nombre, descripcion, fechas y enlace externo
- Sidebar con proximos eventos y leyenda de tipos
- Los admins ven boton "Gestionar eventos" directo
- Widget en la sidebar de la home con los proximos
  3 eventos y mini-fecha visual con color del tipo
- Panel Admin -> Calendario para crear, editar y
  eliminar eventos con:
  * Nombre, tipo, descripcion y color
  * Fecha de inicio y fin (datetime)
  * URL externa opcional
  * Opcion de notificar a todos los usuarios
  * Activar/desactivar evento
- Notificacion automatica a todos los usuarios
  activos al crear un evento si se marca la opcion
- Enlace /calendario/ en el submenu de navegacion
- Tabla: w_eventos
- Archivos nuevos: c.calendario.php, calendario.php,
  t.calendario.tpl, m.home_calendario.tpl,
  m.admin_calendario.tpl
- Modificados: admin.php, index.php, .htaccess,
  t.admin.tpl, m.admin_sidemenu.tpl, m.submenu.tpl,
  posts.php
- Movido del submenu al menu principal como pestana
  propia para no competir con el selector de
  categorias, mostrando solo el icono junto a TOPs

------------------------------------------------------
  CORRECCION — FILTRO DE PAISES SUPERPUESTO
------------------------------------------------------
- El panel desplegable de filtro por paises se
  superponia encima del buscador de la home en
  lugar de empujar el contenido hacia abajo
- Corregido el z-index del modulo de busqueda
  (search_box) para que el panel de paises quede
  siempre por delante al desplegarse
- El selector de categorias ya no se desplaza al
  abrir o cerrar el filtro de paises

------------------------------------------------------
  MEJORA — PORTAL /mi/ REDISEÑADO
------------------------------------------------------
- Foto de portada a todo ancho con degradado azul
  por defecto o imagen personalizada del usuario
- Avatar circular sobre la portada con boton de
  camara para cambiarlo directamente
- Nombre, rango y puntos visibles bajo el avatar
- Stats clicables: posts, comentarios, seguidores
  y siguiendo con numeros grandes y badge de puntos
- Layout en 2 columnas: menu rapido con iconos de
  colores + stats + fotos + posts visitados (izq),
  muro y feed central (derecha)
- Tabs modernizados con iconos FA7: Noticias,
  Actividad, Mis posts y Favoritos
- Botones "Ver perfil" y "Editar perfil" en la
  cabecera del perfil
- Menu rapido: Avisos, Mensajes, Agregar post,
  Agregar foto, Retos, Misiones, Mi cuenta y
  Cerrar sesion con iconos de colores

------------------------------------------------------
  MEJORA — MODULOS DEL PORTAL REDISEÑADOS
------------------------------------------------------
- Formulario del muro: botones Estado/Foto/Enlace/
  Video con colores diferenciados (azul, verde,
  amarillo, rojo), textarea con avatar del usuario
  y boton Compartir moderno
- Cards de publicaciones: fondo blanco, bordes
  redondeados, avatar circular 42px y sombra suave
- Modulo de estadisticas: grid 2 columnas con iconos
  de colores e indicador verde "en vivo"
- Modulo de ultimas fotos: imagen a pantalla completa
  con gradiente y titulo superpuesto, flechas con
  efecto glassmorphism
- Modulo de posts visitados: numeracion en circulo
  azul, hover suave y estado vacio con icono

------------------------------------------------------
  MEJORA — SUBMENU ACTUALIZADO
------------------------------------------------------
- "Agregar Post" renombrado a "Publicar" con
  icono fa-pen-to-square
- Bloque de filtro por paises movido fuera del ul
  para no desplazar el selector de categorias
- Panel de paises oculto por defecto con display:none

------------------------------------------------------
  MEJORA — PANEL MOD TOOLS REDISEÑADO
------------------------------------------------------
- Cabecera con degradado naranja e icono de escudo
- IP en estilo monospace con fondo azul clicable
- Botones "Editar usuario" y "Editar post" para
  administradores
- Bordes y fondo mas limpios y modernos

------------------------------------------------------
  MEJORA — PAGINA MI CUENTA REDISEÑADA
------------------------------------------------------
- Layout moderno con sidebar izquierda fija con avatar
  circular, barra de progreso y navegacion por secciones
- Navegacion vertical con iconos por pestanas:
  Mi cuenta, Perfil, Privacidad, Contrasena,
  Cambiar nick y Bloqueados
- Barra de progreso del perfil con gradiente azul
- Boton de camara sobre el avatar para editarlo
- Campos y formularios con nuevo estilo moderno

------------------------------------------------------
  MEJORA — MARCOS DE AVATAR POR RANGO
------------------------------------------------------
- Los avatares en posts muestran un marco de color
  segun el rango del autor
- Administrador: marco rojo brillante + badge rojo
- Moderador: marco naranja + badge naranja
- Usuario Avanzado: marco cyan + badge
- Gran Usuario: marco verde + badge
- Usuario Legendario: marco naranja oscuro + badge
- Usuario Normal y Novato: marco neutro sin badge
- Avatar circular con efecto de escala al pasar el raton

------------------------------------------------------
  NUEVO — BUSCADOR CON AUTOCOMPLETADO PREDICTIVO
------------------------------------------------------
- Mientras escribes en el buscador aparece un dropdown
  en tiempo real con posts y usuarios sugeridos
- Busqueda de posts por titulo con puntuacion relevante
- Busqueda de usuarios por nick con puntos
- Navegacion con flechas y Enter para seleccionar
- Sin necesidad de pulsar buscar — resultados al instante
- Disponible en el widget de la sidebar y en /buscador/

------------------------------------------------------
  NUEVO — SISTEMA DE MENCIONES (@usuario)
------------------------------------------------------
- Escribe @usuario en cualquier comentario para mencionar
- Autocompletado de usuarios al escribir @ con avatar
- El usuario mencionado recibe notificacion automatica
- Compatible con el sistema de notificaciones existente
- Navegacion con flechas y Tab para seleccionar mencion

------------------------------------------------------
  CORRECCION — TOOLTIPS "undefined" EN PANELES ADMIN
------------------------------------------------------
- Los botones de accion (Editar, Cambiar estado, Borrar)
  en los listados del admin mostraban literalmente
  "undefined" al pasar el raton por encima
- Causa: el plugin tipsy se inicializaba sobre el <img>
  en vez de sobre el <a> que tiene el atributo title real
- Corregido el selector de inicializacion de tipsy
- Archivos modificados: funciones.js, acciones.js
- Afecta a todos los paneles admin que usan la clase
  admin_actions, no solo al de noticias

------------------------------------------------------
  CORRECCION — FATAL ERROR getAvatarUrl() EN PORTAL/PERFIL
------------------------------------------------------
- Error fatal "Call to a member function getAvatarUrl()
  on null" al entrar al portal (/mi/) o al perfil de
  un usuario
- Causa: a esos controladores les faltaba pasar la
  clase tsCore a Smarty, por lo que llegaba como null
  a la plantilla
- Corregido: anadido $smarty->assign("tsCore", $tsCore)
  en ambos controladores
- Archivos modificados: portal.php, perfil.php

------------------------------------------------------
  CORRECCION — BOTONES SEGUIR/FAVORITOS SIN LOGUEAR
------------------------------------------------------
- Los botones "Seguir Usuario", "Seguir Post",
  "Agregar a Favoritos" y "Enviar mensaje privado"
  sin estar logueado abrian el popup antiguo de
  registro, ya eliminado al pasar a paginas dedicadas
- Quedaban rotos (no hacian nada) en vez de llevar
  al registro
- Corregido: ahora redirigen a la pagina /registro/
- Archivos modificados: m.posts_autor.tpl,
  m.posts_metadata.tpl

------------------------------------------------------
  CORRECCION — MENSAJE "CONTRASEÑAS NO COINCIDEN" CORTADO
------------------------------------------------------
- En c.registro.php varios mensajes de error
  (contraseñas, codigo de invitacion, captcha, nick)
  usaban el nombre del campo como prefijo en vez del
  codigo numerico estandar ("0: "), lo que cortaba el
  texto a la mitad (ej: "sword: ..." en vez de
  "Las contraseñas no coinciden.")
- password2 tampoco llegaba siempre al servidor en
  el envio final del formulario
- Añadida validacion en vivo al escribir en "Repetir
  contraseña", igual que ya pasaba con el nick
- Unificado el JS del registro en un unico archivo
  (registro.js), eliminando el script duplicado que
  habia dentro de t.registro.tpl
- Archivos modificados: c.registro.php, registro.js,
  t.registro.tpl

------------------------------------------------------
  CORRECCION — LOGIN ABRIA EL DESPLEGABLE DEL NAVEGADOR SOLO
------------------------------------------------------
- t.login.tpl ponia el foco automaticamente en el campo
  de usuario al cargar la pagina (autofocus)
- Esto disparaba el desplegable de autocompletado y
  credenciales guardadas del navegador justo debajo de
  la barra de direcciones, sin que el usuario tocara nada
- Quitado el autofocus — el desplegable solo aparece
  ahora si el usuario hace clic manualmente en el campo,
  que es el comportamiento normal y esperado
- Archivos modificados: t.login.tpl

------------------------------------------------------
  NUEVO — ANUNCIOS POR CATEGORIA (ANUNCIOS CONTEXTUALES)
------------------------------------------------------
- Desde Admin -> Publicidad se puede configurar un
  codigo de anuncio o afiliado distinto para cada
  categoria, en los 4 tamaños habituales: 300x250,
  468x60, 160x600 y 728x90
- Si una categoria no tiene nada configurado, sigue
  usando el anuncio global de siempre (sin romper nada
  de lo que ya tenias)
- Pensado para comunidades de nicho que prefieren
  enlaces de afiliados relacionados con su tematica en
  vez de anuncios genericos de redes como AdSense
- El admin elige la categoria de un desplegable, pega
  los codigos y guarda — todo por AJAX, sin recargar
- Tabla nueva: w_categoria_ads
- Nuevo plugin de Smarty {get_cat_ad} en
  inc/smarty/plugins/ que decide que anuncio mostrar
  sin tener que tocar cada controlador
- Archivos nuevos: function.get_cat_ad.php,
  ajax.catads.php
- Archivos modificados: m.admin_publicidad.tpl,
  m.global_ads_300.tpl, m.global_ads_468.tpl,
  m.global_ads_160.tpl, m.global_ads_728.tpl,
  funciones.js, acciones.js, database.php
- De paso corregido: m.global_ads_468.tpl no leia el
  codigo de anuncio global configurado por el admin
  (siempre mostraba la imagen fija de PHPost), y
  m.global_ads_160.tpl no tenia el aviso "ANUNCIA AQUI"
  cuando estaba vacio como los demas tamaños

------------------------------------------------------
  FIX — ANUNCIOS POR CATEGORIA: ERROR FATAL DE SMARTY
------------------------------------------------------
- Las plantillas m.global_ads_300/468/160/728.tpl
  usaban una sintaxis de Smarty no valida para llamar
  al plugin get_cat_ad (estilo llamada de funcion PHP
  dentro de un {assign}), algo que Smarty no soporta
- Esto provocaba un error fatal de compilacion en
  cualquier pagina que mostrara anuncios (home, posts...)
- Corregido usando la sintaxis correcta de Smarty para
  plugins de tipo funcion: {get_cat_ad parametros
  assign="variable"}
- Archivos modificados: m.global_ads_300.tpl,
  m.global_ads_468.tpl, m.global_ads_160.tpl,
  m.global_ads_728.tpl

------------------------------------------------------
  NUEVO — BUSCADOR DE PELICULAS Y SERIES (TMDB)
------------------------------------------------------
- Boton "Insertar pelicula" en el editor de posts
  que abre un buscador integrado con The Movie Database
- Busca por titulo de pelicula o serie en tiempo real
- Muestra hasta 8 resultados con poster, titulo,
  tipo (pelicula/serie), año y nota
- Al elegir un resultado se inserta en el editor una
  tarjeta completa con:
    * Poster oficial
    * Titulo y año
    * Generos
    * Sinopsis en español
    * Director (o creador si es serie)
    * Nota de TMDB con enlace a la ficha completa
- Compatible con peliculas y series de TV
- Solo aparece el boton si hay clave TMDB configurada
- Como configurarlo:
    1. Registro gratuito en themoviedb.org
    2. Configuracion -> API -> solicitar clave
       (tipo Developer, aprobacion instantanea)
    3. Admin -> Configuracion -> TMDB -> pegar clave
- Archivos modificados: m.admin_configs.tpl,
  main_header.tpl, m.agregar_form.tpl,
  funciones.js, acciones.js

------------------------------------------------------
  NUEVO — MODERACION ASISTIDA POR IA
------------------------------------------------------
- Boton "Analizar con IA" en el panel de denuncias del
  mod, disponible para posts, fotos, usuarios,
  comunidades, temas y mensajes privados
- Al pulsar el boton, la IA analiza el contenido
  denunciado y devuelve:
    * Analisis del contenido (2-3 frases)
    * Tipo de infraccion detectada (o ninguna)
    * Recomendacion: Eliminar / Ocultar /
      Advertir al usuario / Sin accion
- El mod siempre tiene la ultima palabra, la IA
  solo asiste
- El boton solo aparece si hay clave de API configurada.
  Si no hay clave, el panel de moderacion no cambia
- Como configurarlo:
    1. Obtener clave en console.anthropic.com
    2. Admin -> Configuracion -> Moderacion asistida por IA
    3. Pegar la clave y guardar
    4. Incluir m.mod_ia_analisis.tpl en las plantillas
       de denuncias que quieras
- Usa el modelo claude-haiku (rapido y economico)
- Archivos nuevos: ajax.ia.php, m.mod_ia_analisis.tpl
- Archivos modificados: m.admin_configs.tpl,
  funciones.js, acciones.js

------------------------------------------------------
  NUEVO — SISTEMA DE BANNERS DE CONTEXTO (SMART BANNERS)
------------------------------------------------------
- Aviso personalizado en la home y en el portal (/mi/)
  que cambia automaticamente segun el estado del usuario
- Solo aparece el banner mas relevante en cada momento,
  siguiendo este orden de prioridad:
    1. Invitado → invitacion a registrarse
    2. Sin actividad 7+ dias → mensaje de vuelta
    3. Reto activo → enlace al ranking y tiempo restante
    4. Misiones sin completar → enlace a /misiones/
    5. Cerca de subir de rango → cuantos puntos faltan
    6. Perfil incompleto (primeros 30 dias) → completar perfil
- Si ninguna condicion aplica, no se muestra nada
- Se puede cerrar con la X; no vuelve a aparecer en
  esa misma visita (sessionStorage)
- Admins y mods excluidos del banner de rango
- Solo hace consultas a la base de datos cuando
  las condiciones anteriores no se cumplen
- Archivos nuevos: c.smartbanner.php, m.smart_banner.tpl
- Archivos modificados: posts.php, portal.php,
  t.home.tpl, t.portal.tpl, funciones.js, acciones.js

------------------------------------------------------
  NUEVO — STICKY DE BIENVENIDA PARA USUARIOS NUEVOS
------------------------------------------------------
- Aviso en el Portal (/mi/) que solo ven las cuentas
  registradas hace 7 dias o menos (plazo ajustable en
  portal.php con $dias_para_considerarse_nuevo)
- Mensaje de bienvenida con accesos directos a
  Completar perfil, Misiones diarias y Ayuda
- Se puede cerrar con la X — una vez cerrado no vuelve
  a aparecer nunca para esa cuenta
- El cierre se guarda en la base de datos del usuario
- Columna nueva: user_welcome_dismissed en u_miembros
- Archivos nuevos: ajax.usuario.php
- Archivos modificados: portal.php, t.portal.tpl,
  funciones.js, acciones.js

------------------------------------------------------
  MEJORA — RESUMEN DE REACCIONES REUBICADO
------------------------------------------------------
- El contador de emojis (ej: ❤️ 1) ya no aparece como
  un elemento suelto dentro de la lista de acciones del
  post, donde parecía un botón huérfano
- Ahora se muestra como texto discreto justo debajo de
  la fila de botones (Seguir Post / Reaccionar)
- Archivos modificados: m.posts_metadata.tpl

------------------------------------------------------
  NUEVO — REACCIONES RAPIDAS CON CONTADORES
------------------------------------------------------
- Boton "Reaccionar" en cada post, junto a Seguir Post,
  Agregar a Favoritos y Denunciar
- Al pasar el raton se despliegan 6 reacciones estilo
  Facebook: Me gusta, Me encanta, Me divierte, Me asombra,
  Me entristece y Me enoja
- Una reaccion por usuario por post: si vuelves a marcar
  la misma se quita, si eliges otra se actualiza
- Resumen junto al boton con los 3 emojis mas usados y
  el total, se actualiza al instante sin recargar
- Totalmente independiente del sistema de votos/puntos:
  no reparte ni quita puntos, es solo un contador de
  interaccion
- Tabla nueva: p_reacciones
- Nuevo metodo reaccionarPost() en c.posts.php y nueva
  accion posts-reaccionar en ajax.posts.php
- Archivos modificados: c.posts.php, ajax.posts.php,
  m.posts_metadata.tpl, funciones.js, acciones.js,
  extras.css, database.php

------------------------------------------------------
  NUEVO — SISTEMA DE RETOS DE COMUNIDAD
------------------------------------------------------
- El admin crea retos desde Admin -> Retos con:
  nombre, tipo de metrica, fechas de inicio/fin,
  premios para los 3 primeros y estado activo/pausado
- 4 tipos de reto: posts publicados, comentarios
  escritos, votos recibidos y nuevos seguidores
- Todos los usuarios compiten automaticamente —
  sin necesidad de inscribirse
- Ranking en tiempo real calculado con las tablas
  existentes del script (p_posts, p_comentarios,
  p_votos, u_follows)
- Widget en la sidebar de la home con el reto activo,
  ranking de los top 5 y barra de progreso de tiempo
- Pagina publica /retos/ con ranking completo (top 10),
  premios y listado de retos anteriores con ganadores
- Al terminar el plazo el sistema cierra el reto
  automaticamente, da los puntos a los ganadores
  y les manda una notificacion en el sitio
- El admin puede cerrar un reto manualmente antes
  de que expire desde el panel
- Historial de retos cerrados con ganadores y
  puntos obtenidos visible en /retos/ y en el admin
- Tablas: w_retos, w_retos_historial
- Archivos nuevos: c.retos.php, inc/php/retos.php,
  t.retos.tpl, modules/m.home_retos.tpl,
  admin_mods/m.admin_retos.tpl
- Modificados: admin.php, index.php, t.home.tpl,
  t.admin.tpl, m.admin_sidemenu.tpl, database.php


=====================================================
  RISUS NOVA 2.0.4 — CHANGELOG
  PHPost | https://phpost.es/
  Fecha: Junio 2026
  @author  Tronlar + PHPost Team
=====================================================
------------------------------------------------------
  NUEVO — SISTEMA DE PREMIOS
------------------------------------------------------
- Reconocimientos especiales para los miembros
- 8 premios por defecto en 4 categorias:
  Participacion, Colaboracion, Especiales y Staff
- El admin da premios a usuarios desde
  Admin -> Premios indicando el nick y un motivo
- Notificacion automatica al usuario al recibirlo
- Los premios aparecen en el perfil del usuario
  como iconos de colores con tooltip
- Pagina /premios/ con todos los disponibles y
  cuantos usuarios los tienen
- Panel Admin -> Premios con 3 pestanas:
  Premios, Categorias e Historial de asignaciones
- Premios con icono Font Awesome 7 y color
  personalizable desde el admin
- Tablas w_premios_cats, w_premios, u_premios
- Archivos: c.premios.php, premios.php,
  ajax.premios.php, t.premios.tpl,
  m.admin_premios.tpl, m.perfil_premios.tpl
------------------------------------------------------
  MEJORA — OPEN GRAPH
------------------------------------------------------
- og:type cambia a "article" en posts individuales
  y "website" en el resto de páginas
- og:url ahora es dinámico — apunta a la URL exacta
  de cada página en lugar de siempre a la home
- Metadatos article:published_time y article:author
  añadidos en posts individuales
- Mejor vista previa al compartir en WhatsApp,
  Discord, Twitter y Telegram
------------------------------------------------------
  NUEVO — NOTIFICACIONES EN DISCORD
------------------------------------------------------
- Aviso automatico en tu servidor de Discord cada
  vez que se publique un post nuevo
- El mensaje incluye titulo, categoria, autor y
  enlace directo al post
- Usa Webhooks nativos de Discord — sin bot externo
  ni instalacion adicional
- URL del Webhook configurable desde
  Admin -> Configuracion -> Discord
- Si lo dejas vacio no se envia nada

Como activarlo:
1. En Discord ve al canal donde quieres los avisos
2. Configuracion del canal -> Integraciones ->
   Webhooks -> Nuevo Webhook
3. Copia la URL del Webhook
4. Pegala en Admin -> Configuracion -> Discord
5. Guarda

- Columna discord_webhook en w_configuracion
------------------------------------------------------
  NUEVO — GOOGLE ANALYTICS 4
------------------------------------------------------
- Soporte para Google Analytics 4 integrado
- ID de medicion (G-XXXXXXXXXX) configurable desde
  Admin -> Configuracion -> Google Analytics 4
- El codigo de seguimiento solo se carga si hay
  ID configurado — si lo dejas vacio no afecta
  al rendimiento
- Columna analytics_id en w_configuracion
------------------------------------------------------
  NUEVO — FEED INTELIGENTE DE TENDENCIAS
------------------------------------------------------
- Nueva pestana "Tendencias" en la home junto a
  "Ultimos posts"
- Algoritmo que combina puntos, comentarios y
  velocidad de crecimiento para ordenar los posts
  mas populares del momento
- Similar al algoritmo hot de Reddit:
  (puntos*3 + comentarios*2) / horas^1.5
- Muestra posts de las ultimas 48 horas
- Los 3 primeros posts tienen badge de posicion
  (oro, plata, bronce) y badge de fuego
- Sin SQL adicional — usa las tablas existentes
- Archivos: m.home_tendencias.tpl (nuevo),
  c.posts.php, posts.php, t.home.tpl (modificados)
------------------------------------------------------
  NUEVO — ZONA DE STREAMING
------------------------------------------------------
- Pagina /streaming/ con player de video y chat
  integrado sin salir de la comunidad
- Compatible con Twitch, YouTube y Kick
- Widget en la sidebar de la home que aparece
  automaticamente cuando hay canal configurado
- Plataforma y nombre del canal configurables
  desde Admin -> Configuracion -> Streaming
- Si no hay canal configurado no aparece nada
- Columnas stream_plataforma y stream_canal
  en w_configuracion
- Archivos: streaming.php, t.streaming.tpl,
  m.home_streaming.tpl
------------------------------------------------------
  NUEVO — MISIONES DIARIAS (GAMIFICACION)
------------------------------------------------------
- Sistema de misiones para mantener a los usuarios
  activos y que vuelvan cada dia
- 9 misiones por defecto incluidas en la instalacion:
  visita diaria, publicar posts, comentar, votar,
  recibir votos y conseguir seguidores
- Progreso automatico al publicar posts, comentar
  y votar — sin configuracion adicional
- Recompensa en puntos al completar cada mision
- Notificacion automatica al usuario al completar
- Misiones diarias y semanales
- Pagina /misiones/ con progreso visual, barras
  de completado y listado de misiones pendientes
- Widget en la sidebar de la home solo para
  usuarios logueados con progreso del dia
- Panel Admin -> Misiones para crear, editar,
  activar/desactivar y eliminar misiones
- Tablas w_misiones y u_misiones
- Archivos: c.misiones.php, misiones.php,
  t.misiones.tpl, m.home_misiones.tpl,
  m.admin_misiones.tpl
------------------------------------------------------
  NUEVO — LOGIN CON GOOGLE
------------------------------------------------------
- Los usuarios pueden iniciar sesion con su cuenta
  de Google desde la pagina /login/
- Vinculacion automatica si el email ya existe
- Creacion automatica de cuenta al primer login
- Client ID y Client Secret configurables desde
  Admin -> Configuracion -> Login con Google
- El boton de Google solo aparece si esta configurado
- Columnas google_client_id y google_client_secret
  en w_configuracion
- Columna user_google_id en u_miembros
- Archivo: google_auth.php → raiz del sitio
------------------------------------------------------
  NUEVO — HONEYPOT ANTI-SPAM
------------------------------------------------------
- Campo invisible en el registro que solo los bots
  rellenan — bloquea registros automatizados
- Activable desde Admin -> Configuracion
- Compatible con Turnstile y pregunta de seguridad
- No delata que hay proteccion al bot
- Columna honeypot_activo en w_configuracion
------------------------------------------------------
  NUEVO — POSTS DESTACADOS
------------------------------------------------------
- El admin puede marcar posts como destacados desde
  Admin -> Posts con un clic
- Pagina /destacados/ con todos los posts destacados
- Widget en la sidebar de la home
- Panel Admin -> Destacados para gestionar y quitar
- Badge estrella dorada en posts destacados
- Columnas post_destacado, post_destacado_fecha y
  post_destacado_admin en p_posts
------------------------------------------------------
  NUEVO — HOME POR CATEGORIAS
------------------------------------------------------
- Modo de visualizacion de la home configurable
  desde Admin -> Configuracion:
  * Normal: todos los posts mezclados (por defecto)
  * Por categorias: bloques separados por seccion
  * Ambos: posts normales + bloques por categoria
- Numero de posts por categoria configurable (1-10)
- Automatico: las categorias nuevas aparecen solas
  sin tocar codigo
- Una sola query para todas las categorias
- Compatible con iconos FA7 por categoria
- Columnas home_mode y home_cat_limit en
  w_configuracion
------------------------------------------------------
  NUEVO — NOTIFICACIONES PUSH
------------------------------------------------------
- Sistema de notificaciones push para el navegador
- Los usuarios reciben avisos aunque no tengan el
  sitio abierto (nuevos comentarios, respuestas...)
- Activacion/desactivacion por el usuario desde
  Mi Cuenta -> Privacidad
- Activacion/desactivacion global desde
  Admin -> Configuracion -> Notificaciones Push
- Requiere HTTPS y claves VAPID configuradas
- Compatible con Chrome, Firefox y Edge
- Archivos: sw.js, push.js, ajax.push.php, c.push.php
- Columnas push_activo, push_vapid_pub, push_vapid_priv
  en w_configuracion
- Columna push_activo en u_perfil
- Tabla w_push_subs para suscripciones
------------------------------------------------------
  NUEVO — SISTEMA DE DONACIONES
------------------------------------------------------
- Pagina dedicada /donaciones/ con diseno moderno
- Compatible con PayPal, Ko-fi, Buy Me a Coffee,
  Patreon y Bizum
- Barra de progreso hacia objetivo mensual
- Lista de ultimos donantes con avatar
- Widget en sidebar de la home (solo si hay metodo
  configurado)
- Enlace automatico en menu de navegacion
- Configuracion desde Admin -> Configuracion
- Archivos: donaciones.php, t.donaciones.tpl,
  m.home_donaciones.tpl
- Columnas don_* en w_configuracion
- Tabla w_donaciones para registro de donantes
------------------------------------------------------
  NUEVO — EMAIL SMTP (PHPMailer)
------------------------------------------------------
- Sistema de email completamente reescrito con
  soporte SMTP via PHPMailer
- Compatible con Gmail, Outlook, Yahoo y el email
  del propio hosting
- Configuracion desde Admin -> Configuracion en la
  nueva seccion "Configuracion de Email (SMTP)"
- Boton de test de envio desde el panel admin
- Fallback automatico a mail() si SMTP no esta
  configurado o falla
- Templates de email modernos en HTML con diseno
  responsive para registro, recuperacion de
  contrasena y bienvenida
- Columnas smtp_host, smtp_user, smtp_pass,
  smtp_port y smtp_from en w_configuracion
------------------------------------------------------
  NUEVO — PREGUNTA DE SEGURIDAD EN REGISTRO
------------------------------------------------------
- Alternativa al Turnstile para hostings sin
  allow_url_fopen activo
- Si hay Turnstile configurado se usa Turnstile;
  si no, se muestra la pregunta de seguridad
- Pregunta y respuesta configurables desde
  Admin -> Configuracion
- Columnas reg_pregunta y reg_respuesta en
  w_configuracion
------------------------------------------------------
  NUEVO — SOPORTE POR TICKETS
------------------------------------------------------
- Sistema completo de soporte en /soporte/
- Prioridades: baja, media, alta, urgente
- Categorias: tecnico, cuenta, contenido, pagos, otro
- Estados: abierto, en proceso, resuelto, cerrado
- Panel admin con estadisticas, filtros por estado
  y tabla con avatar del usuario que envio el ticket
- Chat estilo mensajeria en la vista del ticket
- Notificacion automatica al usuario al responder
- Valoracion del ticket por el usuario (resuelto /
  sin resolver)
- Tablas s_tickets y s_mensajes
------------------------------------------------------
  NUEVO — ZONA VIP
------------------------------------------------------
- Pagina exclusiva /vip/ para miembros con acceso
- Panel admin en /admin/vip con estadisticas
- Asignacion VIP manual por usuario o por rango
  completo con fecha de expiracion opcional
- Posts VIP — solo visibles para miembros VIP,
  excluidos de la home, tops y comentarios recientes
- Badge dorado en el perfil de usuarios VIP
- Notificacion automatica al asignar acceso VIP
- Fallback automatico si el mod no esta instalado
  (detecta columna post_vip via information_schema)
- Tablas u_vip y u_vip_rangos
------------------------------------------------------
  NUEVO — LOGIN Y REGISTRO EN PAGINAS DEDICADAS
------------------------------------------------------
- Paginas /login/ y /registro/ con diseno carta
  aerea estilo dos columnas
- Registro en 3 pasos (Cuenta, Perfil, Confirmar)
  con validacion en tiempo real de nick y email
- Validacion de contrasenas tanto en JS como en
  servidor (c.registro.php)
- Fix critico: id="password" duplicado en login_box
  causaba error "Las contrasenas no coinciden"
  corregido cambiando a id="login_password"
------------------------------------------------------
  NUEVO — OTRAS FUNCIONES
------------------------------------------------------
- Filtro de posts por pais del autor con banderas
- Filtro de posts por inicial del titulo (A-Z, 0-9)
- Buscador con 3 pestanas: Posts, Comunidades y Google
- Sistema En Vivo en /en-vivo/ con feed de actividad
  en tiempo real (posts, comentarios, comunidades,
  nuevos usuarios) y widget en columna lateral
- Estrellas de reputacion con FA7 — niveles Normal
  y Premium, detecta Admin y Moderador por rango
- Lazy loading automatico en imagenes de posts
- Subida de imagenes en CKEditor 5 via
  upload_images.php con validacion GD
- Meta description dinamica — slogan en home,
  extracto de 160 chars en posts individuales
------------------------------------------------------
  CORRECCIONES
------------------------------------------------------
- perfil.js: global_data is not defined corregido
  con retry de 100ms hasta que este disponible
- c.registro.php: estado (region) ahora opcional,
  no bloquea el formulario si no carga
- m.post_autor.tpl: botones seguir/dejar de seguir
  ya no se renderizan los dos a la vez
- getLastPosts: INNER JOIN cuando hay filtro de pais
- getLastPosts: user_rango != 3 para excluir
  usuarios eliminados de los resultados
- posts.php: En Vivo se carga condicionalmente con
  file_exists() — no falla si no esta instalado
- registro.js: eliminados console.log y alert de
  debug que quedaron de pruebas de desarrollo
------------------------------------------------------
  PARCHE 2.0.4 — SEGURIDAD
------------------------------------------------------
- Headers HTTP de seguridad en header.php:
  X-Frame-Options, X-Content-Type-Options,
  X-XSS-Protection, Referrer-Policy,
  Permissions-Policy y HSTS (solo HTTPS)
- Cookies de sesion protegidas con httponly y
  samesite=Lax
- Rate limiting en login: maximo 5 intentos por
  nick y 10 por IP en 10 minutos
  Tabla w_login_attempts anadida
- Clase tsSeguridad con prepared statements,
  sanitizacion mejorada y tokens CSRF
- getIP() corregido: no acepta HTTP_CLIENT_IP
- X_FORWARDED_FOR corregido en c.posts.php,
  c.fotos.php, c.cuenta.php, c.mensajes.php
- REMOTE_ADDR validado con FILTER_VALIDATE_IP
- Inputs sin sanitizar corregidos en c.mensajes.php
- Contrasenas limitadas a 72 caracteres en
  c.cuenta.php para evitar DoS por bcrypt
- XSS corregido en ajax.borradores.php
------------------------------------------------------
  PARCHE 2.0.3
------------------------------------------------------
- Corregido array $files indefinido en ajax.feed.php
- Corregido nivel de acceso en ajax.feed.php
- Corregida URL AJAX del comprobador de versiones
------------------------------------------------------
  PARCHE 2.0.1
------------------------------------------------------
- public array $permisos declarado como propiedad
  tipada en tsUser (PHP 8 estricto)
- jQuery movido al head para scripts inline del body
- Font Awesome 6 eliminado de m.agregar.form.tpl
- Columnas p_fondo y p_fondoper anadidas a u_perfil
------------------------------------------------------
  COMPATIBILIDAD PHP 8
------------------------------------------------------
- Compatibilidad total con PHP 8.3+
- Migracion de Smarty 2 a Smarty 4.5.6
- Contrasenas migradas de MD5 a bcrypt con migracion
  automatica al iniciar sesion
- Sistema de sesiones con random_bytes()
- Correcciones de TypeError, warnings y claves
  indefinidas en todo el sistema
------------------------------------------------------
  BASE DE DATOS
------------------------------------------------------
- Tabla w_push_subs para suscripciones push
- Tabla w_donaciones para registro de donantes
- Columnas push en w_configuracion:
  push_activo, push_vapid_pub, push_vapid_priv
- Columna push_activo en u_perfil
- Columnas donaciones en w_configuracion:
  don_paypal, don_kofi, don_bmac, don_patreon,
  don_bizum, don_transferencia, don_objetivo,
  don_recaudado
- Columnas SMTP en w_configuracion:
  smtp_host, smtp_user, smtp_pass, smtp_port,
  smtp_from
- Columnas pregunta seguridad en w_configuracion:
  reg_pregunta, reg_respuesta
- Tablas nuevas: s_tickets, s_mensajes (soporte),
  u_vip, u_vip_rangos (zona vip),
  w_login_attempts (rate limiting)
- Columna post_vip en p_posts
- Indices FULLTEXT en p_posts
- Columna post_image en p_posts
- Columna user_pwtype en u_miembros
------------------------------------------------------
  OTROS
------------------------------------------------------
- Nuevos botones de compartir en redes sociales
- RSS feed implementado
- Sistema de avatares mejorado
- Sitemap XML dinamico en /sitemap.xml
- Nuevo editor CKEditor 5
- Instalador completamente reescrito (6 pasos)
- Limpieza de archivos y librerias innecesarias
=====================================================
  NOTAS DE ACTUALIZACION DESDE VERSION ANTERIOR
=====================================================
1. Sube todos los archivos al servidor
2. Ejecuta el upgrade desde /upgrade/
3. Ejecuta en phpMyAdmin:

   -- Sistema de Premios
   CREATE TABLE IF NOT EXISTS w_premios_cats (
     cat_id int(11) NOT NULL AUTO_INCREMENT,
     cat_nombre varchar(80) NOT NULL DEFAULT '',
     cat_orden int(11) NOT NULL DEFAULT 0,
     PRIMARY KEY (cat_id)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
   INSERT INTO w_premios_cats (cat_nombre, cat_orden) VALUES
   ('Participación',1),('Colaboración',2),('Especiales',3),('Staff',4);
   CREATE TABLE IF NOT EXISTS w_premios (
     premio_id int(11) NOT NULL AUTO_INCREMENT,
     cat_id int(11) NOT NULL DEFAULT 1,
     nombre varchar(100) NOT NULL DEFAULT '',
     descripcion varchar(255) NOT NULL DEFAULT '',
     icono varchar(60) NOT NULL DEFAULT 'fa-solid fa-award',
     color varchar(20) NOT NULL DEFAULT '#f39c12',
     activo tinyint(1) NOT NULL DEFAULT 1,
     orden int(11) NOT NULL DEFAULT 0,
     PRIMARY KEY (premio_id)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
   CREATE TABLE IF NOT EXISTS u_premios (
     up_id int(11) NOT NULL AUTO_INCREMENT,
     user_id int(11) NOT NULL,
     premio_id int(11) NOT NULL,
     admin_id int(11) NOT NULL DEFAULT 1,
     mensaje varchar(255) NOT NULL DEFAULT '',
     fecha int(11) NOT NULL DEFAULT 0,
     PRIMARY KEY (up_id)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

   -- Discord Webhook
   ALTER TABLE w_configuracion
     ADD COLUMN IF NOT EXISTS discord_webhook varchar(255) NOT NULL DEFAULT '';

   -- Repositorio de Complementos
   CREATE TABLE IF NOT EXISTS w_plugins (
     plugin_id int(11) NOT NULL AUTO_INCREMENT,
     slug varchar(60) NOT NULL DEFAULT '',
     nombre varchar(100) NOT NULL DEFAULT '',
     version varchar(20) NOT NULL DEFAULT '',
     autor varchar(60) NOT NULL DEFAULT '',
     modo varchar(10) NOT NULL DEFAULT 'sandbox',
     archivos text NULL,
     activo tinyint(1) NOT NULL DEFAULT 1,
     fecha_instalado int(11) NOT NULL DEFAULT 0,
     fecha_actualizado int(11) NOT NULL DEFAULT 0,
     PRIMARY KEY (plugin_id),
     UNIQUE KEY slug (slug)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
   ALTER TABLE w_configuracion
     ADD COLUMN IF NOT EXISTS plugins_catalog_url varchar(255) NOT NULL DEFAULT 'https://phpost.es/repo/catalog.json';

   -- Notas de Staff
   CREATE TABLE IF NOT EXISTS w_notas_staff (
     n_id int(11) NOT NULL AUTO_INCREMENT,
     n_user int(11) NOT NULL DEFAULT '0',
     n_texto text NOT NULL,
     n_fecha int(11) NOT NULL DEFAULT '0',
     PRIMARY KEY (n_id),
     KEY idx_fecha (n_fecha)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

   -- Bump de posts editados
   ALTER TABLE p_posts
     ADD COLUMN IF NOT EXISTS post_bump_fecha int(10) NOT NULL DEFAULT '0';

   -- Posts conjuntos (coautoria)
   ALTER TABLE p_posts
     ADD COLUMN IF NOT EXISTS post_usec int(11) NOT NULL DEFAULT '0',
     ADD COLUMN IF NOT EXISTS post_usec_status tinyint(1) NOT NULL DEFAULT '0';

   -- Google Analytics 4
   ALTER TABLE w_configuracion
     ADD COLUMN IF NOT EXISTS analytics_id varchar(30) NOT NULL DEFAULT '';

   -- Streaming
   ALTER TABLE w_configuracion
     ADD COLUMN IF NOT EXISTS stream_plataforma varchar(20) NOT NULL DEFAULT 'twitch',
     ADD COLUMN IF NOT EXISTS stream_canal varchar(100) NOT NULL DEFAULT '';

   -- Misiones Diarias
   CREATE TABLE IF NOT EXISTS w_misiones (
     mision_id int(11) NOT NULL AUTO_INCREMENT,
     nombre varchar(100) NOT NULL DEFAULT '',
     descripcion varchar(255) NOT NULL DEFAULT '',
     tipo varchar(30) NOT NULL DEFAULT 'posts',
     objetivo int(11) NOT NULL DEFAULT 1,
     puntos int(11) NOT NULL DEFAULT 10,
     periodo varchar(10) NOT NULL DEFAULT 'diario',
     activo tinyint(1) NOT NULL DEFAULT 1,
     orden int(11) NOT NULL DEFAULT 0,
     PRIMARY KEY (mision_id)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
   INSERT INTO w_misiones (nombre, descripcion, tipo, objetivo, puntos, periodo, activo, orden) VALUES
   ('Visita diaria','Entra a la comunidad hoy','visita',1,5,'diario',1,1),
   ('Primer post del día','Publica al menos 1 post hoy','posts',1,20,'diario',1,2),
   ('Publicador activo','Publica 3 posts hoy','posts',3,40,'diario',1,3),
   ('Comentarista','Comenta al menos 1 post hoy','comentarios',1,10,'diario',1,4),
   ('Participación activa','Haz 5 comentarios hoy','comentarios',5,25,'diario',1,5),
   ('Votante','Vota 3 posts hoy','votos_dados',3,10,'diario',1,6),
   ('Contenido valorado','Recibe 3 votos positivos hoy','votos_recibidos',3,15,'diario',1,7),
   ('Influencer semanal','Consigue 5 nuevos seguidores esta semana','seguidores',5,50,'semanal',1,8),
   ('Creador semanal','Publica 10 posts esta semana','posts',10,80,'semanal',1,9);
   CREATE TABLE IF NOT EXISTS u_misiones (
     um_id int(11) NOT NULL AUTO_INCREMENT,
     user_id int(11) NOT NULL,
     mision_id int(11) NOT NULL,
     progreso int(11) NOT NULL DEFAULT 0,
     completada tinyint(1) NOT NULL DEFAULT 0,
     puntos_dados tinyint(1) NOT NULL DEFAULT 0,
     fecha date NOT NULL,
     PRIMARY KEY (um_id),
     UNIQUE KEY user_mision_fecha (user_id, mision_id, fecha),
     KEY user_id (user_id),
     KEY fecha (fecha)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

------------------------------------------------------
  INSTALACION — INDICES FULLTEXT NECESARIOS
------------------------------------------------------
Ejecutar en phpMyAdmin para el buscador con autocompletado:

   ALTER TABLE `p_posts` ADD FULLTEXT INDEX `ft_post_title` (`post_title`);
   ALTER TABLE `p_posts` ADD FULLTEXT INDEX `ft_post_tags` (`post_tags`);

   -- Login con Google
   ALTER TABLE w_configuracion
     ADD COLUMN IF NOT EXISTS google_client_id varchar(255) NOT NULL DEFAULT '',
     ADD COLUMN IF NOT EXISTS google_client_secret varchar(255) NOT NULL DEFAULT '';
   ALTER TABLE u_miembros
     ADD COLUMN IF NOT EXISTS user_google_id varchar(50) NOT NULL DEFAULT '';

   -- Honeypot
   ALTER TABLE w_configuracion
     ADD COLUMN IF NOT EXISTS honeypot_activo tinyint(1) NOT NULL DEFAULT 0;

   -- Posts Destacados
   ALTER TABLE p_posts
     ADD COLUMN IF NOT EXISTS post_destacado tinyint(1) NOT NULL DEFAULT 0,
     ADD COLUMN IF NOT EXISTS post_destacado_fecha int(11) NOT NULL DEFAULT 0,
     ADD COLUMN IF NOT EXISTS post_destacado_admin int(11) NOT NULL DEFAULT 0;

   -- Home por categorias
   ALTER TABLE w_configuracion
     ADD COLUMN IF NOT EXISTS home_mode tinyint(1) NOT NULL DEFAULT 0,
     ADD COLUMN IF NOT EXISTS home_cat_limit tinyint(2) NOT NULL DEFAULT 5;

   -- Push Notifications
   ALTER TABLE w_configuracion
     ADD COLUMN IF NOT EXISTS push_activo tinyint(1) NOT NULL DEFAULT 0,
     ADD COLUMN IF NOT EXISTS push_vapid_pub varchar(255) NOT NULL DEFAULT '',
     ADD COLUMN IF NOT EXISTS push_vapid_priv varchar(255) NOT NULL DEFAULT '';
   ALTER TABLE u_perfil
     ADD COLUMN IF NOT EXISTS push_activo tinyint(1) NOT NULL DEFAULT 1;
   CREATE TABLE IF NOT EXISTS w_push_subs (
     sub_id int(11) NOT NULL AUTO_INCREMENT,
     user_id int(11) NOT NULL,
     endpoint text NOT NULL,
     p256dh varchar(255) NOT NULL DEFAULT '',
     auth varchar(100) NOT NULL DEFAULT '',
     fecha int(11) NOT NULL DEFAULT 0,
     PRIMARY KEY (sub_id),
     KEY user_id (user_id)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

   -- Donaciones
   ALTER TABLE w_configuracion
     ADD COLUMN IF NOT EXISTS don_paypal varchar(255) NOT NULL DEFAULT '',
     ADD COLUMN IF NOT EXISTS don_kofi varchar(255) NOT NULL DEFAULT '',
     ADD COLUMN IF NOT EXISTS don_bmac varchar(255) NOT NULL DEFAULT '',
     ADD COLUMN IF NOT EXISTS don_patreon varchar(255) NOT NULL DEFAULT '',
     ADD COLUMN IF NOT EXISTS don_bizum varchar(20) NOT NULL DEFAULT '',
     ADD COLUMN IF NOT EXISTS don_transferencia tinyint(1) NOT NULL DEFAULT 0,
     ADD COLUMN IF NOT EXISTS don_objetivo decimal(8,2) NOT NULL DEFAULT 0.00,
     ADD COLUMN IF NOT EXISTS don_recaudado decimal(8,2) NOT NULL DEFAULT 0.00;
   CREATE TABLE IF NOT EXISTS w_donaciones (
     don_id int(11) NOT NULL AUTO_INCREMENT,
     don_nombre varchar(64) NOT NULL DEFAULT '',
     don_cantidad decimal(8,2) NOT NULL DEFAULT 0.00,
     don_metodo varchar(30) NOT NULL DEFAULT '',
     don_mensaje varchar(255) NOT NULL DEFAULT '',
     don_fecha int(11) NOT NULL DEFAULT 0,
     don_activo tinyint(1) NOT NULL DEFAULT 1,
     don_mostrar_cantidad tinyint(1) NOT NULL DEFAULT 1,
     PRIMARY KEY (don_id)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

   -- SMTP
   ALTER TABLE w_configuracion
     ADD COLUMN IF NOT EXISTS smtp_host varchar(120) NOT NULL DEFAULT '',
     ADD COLUMN IF NOT EXISTS smtp_user varchar(120) NOT NULL DEFAULT '',
     ADD COLUMN IF NOT EXISTS smtp_pass varchar(120) NOT NULL DEFAULT '',
     ADD COLUMN IF NOT EXISTS smtp_port smallint(5) NOT NULL DEFAULT 587,
     ADD COLUMN IF NOT EXISTS smtp_from varchar(120) NOT NULL DEFAULT '';

   -- Pregunta de seguridad
   ALTER TABLE w_configuracion
     ADD COLUMN IF NOT EXISTS reg_pregunta varchar(255) NOT NULL DEFAULT '',
     ADD COLUMN IF NOT EXISTS reg_respuesta varchar(100) NOT NULL DEFAULT '';

   -- Zona VIP
   CREATE TABLE IF NOT EXISTS u_vip (
     vip_id int(11) NOT NULL AUTO_INCREMENT,
     user_id int(11) NOT NULL,
     vip_tipo tinyint(1) NOT NULL DEFAULT 1,
     vip_inicio int(11) NOT NULL DEFAULT 0,
     vip_expira int(11) NOT NULL DEFAULT 0,
     vip_notas varchar(255) NOT NULL DEFAULT '',
     vip_admin int(11) NOT NULL DEFAULT 0,
     PRIMARY KEY (vip_id),
     UNIQUE KEY user_id (user_id)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
   CREATE TABLE IF NOT EXISTS u_vip_rangos (
     vr_id int(11) NOT NULL AUTO_INCREMENT,
     rango_id int(11) NOT NULL,
     PRIMARY KEY (vr_id),
     UNIQUE KEY rango_id (rango_id)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
   ALTER TABLE p_posts ADD COLUMN IF NOT EXISTS post_vip tinyint(1) NOT NULL DEFAULT 0;

   -- Soporte
   CREATE TABLE IF NOT EXISTS s_tickets (
     ticket_id int(11) NOT NULL AUTO_INCREMENT,
     user_id int(11) NOT NULL,
     asunto varchar(120) NOT NULL DEFAULT '',
     categoria varchar(30) NOT NULL DEFAULT 'tecnico',
     prioridad tinyint(1) NOT NULL DEFAULT 2,
     estado tinyint(1) NOT NULL DEFAULT 1,
     fecha int(11) NOT NULL DEFAULT 0,
     fecha_update int(11) NOT NULL DEFAULT 0,
     leido_admin tinyint(1) NOT NULL DEFAULT 0,
     leido_user tinyint(1) NOT NULL DEFAULT 1,
     valoracion tinyint(1) NOT NULL DEFAULT 0,
     PRIMARY KEY (ticket_id)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
   CREATE TABLE IF NOT EXISTS s_mensajes (
     msg_id int(11) NOT NULL AUTO_INCREMENT,
     ticket_id int(11) NOT NULL,
     user_id int(11) NOT NULL,
     cuerpo text NOT NULL,
     es_admin tinyint(1) NOT NULL DEFAULT 0,
     fecha int(11) NOT NULL DEFAULT 0,
     PRIMARY KEY (msg_id)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

   -- Rate limiting
   CREATE TABLE IF NOT EXISTS w_login_attempts (
     id int(11) NOT NULL AUTO_INCREMENT,
     ip varchar(45) NOT NULL DEFAULT '',
     nick varchar(50) NOT NULL DEFAULT '',
     fecha int(11) NOT NULL DEFAULT 0,
     PRIMARY KEY (id)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

   -- Indices FULLTEXT
   ALTER TABLE p_posts ADD FULLTEXT KEY ft_tags (post_tags);
   ALTER TABLE p_posts ADD FULLTEXT KEY ft_title_body (post_title, post_body);

   -- Fuente del contenido al publicar post
   ALTER TABLE p_posts ADD COLUMN IF NOT EXISTS post_fuente varchar(255) NOT NULL DEFAULT 'author';

   -- Usuario de la semana
   ALTER TABLE w_configuracion
     ADD COLUMN IF NOT EXISTS c_usersem tinyint(1) NOT NULL DEFAULT 1,
     ADD COLUMN IF NOT EXISTS cron_user_semana int(11) NOT NULL DEFAULT 0;
   CREATE TABLE IF NOT EXISTS w_user_semana (
     usem_id int(11) NOT NULL AUTO_INCREMENT,
     usem_user int(11) NOT NULL DEFAULT 0,
     usem_inicio int(10) NOT NULL DEFAULT 0,
     usem_fin int(10) NOT NULL DEFAULT 0,
     usem_puntuacion int(11) NOT NULL DEFAULT 0,
     usem_calculado int(10) NOT NULL DEFAULT 0,
     PRIMARY KEY (usem_id)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

   -- Login con Discord
   ALTER TABLE u_miembros
     ADD COLUMN IF NOT EXISTS user_discord_id varchar(50) NOT NULL DEFAULT '';
   ALTER TABLE w_configuracion
     ADD COLUMN IF NOT EXISTS discord_client_id varchar(50) NOT NULL DEFAULT '',
     ADD COLUMN IF NOT EXISTS discord_client_secret varchar(100) NOT NULL DEFAULT '';

   -- Avatar GIF
   ALTER TABLE u_perfil
     ADD COLUMN IF NOT EXISTS user_avatar_gif varchar(500) NOT NULL DEFAULT '',
     ADD COLUMN IF NOT EXISTS user_avatar_gif_activo tinyint(1) NOT NULL DEFAULT '1',
     ADD COLUMN IF NOT EXISTS user_avatar_gif_bloqueado tinyint(1) NOT NULL DEFAULT '0';
   -- NOTA: en sitios ya instalados, activa el permiso a mano en
   -- Admin -> Rangos -> casilla "Avatar GIF" (en instalaciones nuevas
   -- ya viene activado de fabrica para todos los rangos)

   -- Posts Limitados para Visitantes
   ALTER TABLE w_configuracion
     ADD COLUMN IF NOT EXISTS c_limite_visitante_activo tinyint(1) NOT NULL DEFAULT '0',
     ADD COLUMN IF NOT EXISTS c_limite_visitante_cantidad int(3) NOT NULL DEFAULT '3';
   CREATE TABLE IF NOT EXISTS w_visitas_limite (
     vl_id int(11) NOT NULL AUTO_INCREMENT,
     vl_ip varchar(45) NOT NULL DEFAULT '',
     vl_total int(11) NOT NULL DEFAULT '0',
     vl_primera int(11) NOT NULL DEFAULT '0',
     vl_ultima int(11) NOT NULL DEFAULT '0',
     PRIMARY KEY (vl_id),
     UNIQUE KEY vl_ip (vl_ip)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

   -- Encuestas en los Posts
   CREATE TABLE IF NOT EXISTS p_encuestas (
     enc_id int(11) NOT NULL AUTO_INCREMENT,
     post_id int(11) NOT NULL,
     pregunta varchar(255) NOT NULL DEFAULT '',
     fecha int(11) NOT NULL DEFAULT 0,
     PRIMARY KEY (enc_id),
     KEY post_id (post_id)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
   CREATE TABLE IF NOT EXISTS p_encuestas_opciones (
     opcion_id int(11) NOT NULL AUTO_INCREMENT,
     enc_id int(11) NOT NULL,
     texto varchar(255) NOT NULL DEFAULT '',
     votos int(11) NOT NULL DEFAULT 0,
     PRIMARY KEY (opcion_id),
     KEY enc_id (enc_id)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
   CREATE TABLE IF NOT EXISTS p_encuestas_votos (
     voto_id int(11) NOT NULL AUTO_INCREMENT,
     enc_id int(11) NOT NULL,
     opcion_id int(11) NOT NULL,
     user_id int(11) NOT NULL,
     fecha int(11) NOT NULL DEFAULT 0,
     PRIMARY KEY (voto_id),
     UNIQUE KEY enc_user (enc_id, user_id)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

   -- Color de categoria en el titulo de los posts
   ALTER TABLE p_categorias
     ADD COLUMN IF NOT EXISTS c_color varchar(20) NOT NULL DEFAULT '#1a6fc4';

   -- Comentario destacado por votos
   ALTER TABLE w_configuracion
     ADD COLUMN IF NOT EXISTS c_com_votos_destacado int(4) NOT NULL DEFAULT '50';

   -- Mensaje de enlaces caidos
   ALTER TABLE p_posts
     ADD COLUMN IF NOT EXISTS post_link tinyint(1) NOT NULL DEFAULT 0;

   -- Sistema de referidos
   ALTER TABLE u_miembros
     ADD COLUMN IF NOT EXISTS user_referidos int(11) NOT NULL DEFAULT 0;

   CREATE TABLE IF NOT EXISTS u_referidos (
     r_id int(11) NOT NULL AUTO_INCREMENT,
     r_referidor int(11) NOT NULL DEFAULT 0,
     r_referido int(11) NOT NULL DEFAULT 0,
     r_fecha int(11) NOT NULL DEFAULT 0,
     PRIMARY KEY (r_id),
     UNIQUE KEY idx_referido (r_referido),
     KEY idx_referidor (r_referidor)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

   -- Usuario Verificado
   ALTER TABLE u_miembros
     ADD COLUMN IF NOT EXISTS user_verificado tinyint(1) NOT NULL DEFAULT 0;

   -- Puntos al aceptar denuncia
   ALTER TABLE w_configuracion
     ADD COLUMN IF NOT EXISTS d_posts int(5) NOT NULL DEFAULT 0,
     ADD COLUMN IF NOT EXISTS d_user int(5) NOT NULL DEFAULT 0,
     ADD COLUMN IF NOT EXISTS d_mensajes int(5) NOT NULL DEFAULT 0,
     ADD COLUMN IF NOT EXISTS d_fotos int(5) NOT NULL DEFAULT 0;

   -- Categorias solo para staff
   ALTER TABLE p_categorias
     ADD COLUMN IF NOT EXISTS c_staff tinyint(1) NOT NULL DEFAULT 0;

   -- Limpieza automatica de visitas y sesiones
   ALTER TABLE w_configuracion
     ADD COLUMN IF NOT EXISTS cron_clean_visitas tinyint(1) NOT NULL DEFAULT 0,
     ADD COLUMN IF NOT EXISTS cron_clean_sessions tinyint(1) NOT NULL DEFAULT 0;

   -- Preguntas Frecuentes administrables
   CREATE TABLE IF NOT EXISTS p_preguntas (
     pregunta_id int(11) NOT NULL AUTO_INCREMENT,
     pregunta_box varchar(255) NOT NULL DEFAULT '',
     respuesta_box text NOT NULL,
     p_orden int(11) NOT NULL DEFAULT 0,
     p_activa tinyint(1) NOT NULL DEFAULT 1,
     PRIMARY KEY (pregunta_id),
     KEY idx_orden (p_orden)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

   -- Log de Moderacion de Usuario
   CREATE TABLE IF NOT EXISTS w_userlog (
     log_id int(11) NOT NULL AUTO_INCREMENT,
     log_user int(11) NOT NULL DEFAULT 0,
     log_type varchar(20) NOT NULL DEFAULT 'nota',
     log_content text NULL,
     log_staff int(11) NOT NULL DEFAULT 0,
     log_date int(11) NOT NULL DEFAULT 0,
     PRIMARY KEY (log_id),
     KEY log_user (log_user)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

   -- Autoguardado de Borradores
   ALTER TABLE p_borradores
     ADD COLUMN IF NOT EXISTS b_links tinyint(1) NOT NULL DEFAULT 0;

   -- Sugerencias de la Comunidad
   CREATE TABLE IF NOT EXISTS w_sugerencias (
     sug_id int(11) NOT NULL AUTO_INCREMENT,
     sug_user int(11) NOT NULL DEFAULT 0,
     sug_titulo varchar(150) NOT NULL DEFAULT '',
     sug_cuerpo text NULL,
     sug_estado varchar(20) NOT NULL DEFAULT 'nueva',
     sug_votos int(11) NOT NULL DEFAULT 0,
     sug_respuesta text NULL,
     sug_staff int(11) NOT NULL DEFAULT 0,
     sug_respuesta_fecha int(11) NOT NULL DEFAULT 0,
     sug_fecha int(11) NOT NULL DEFAULT 0,
     PRIMARY KEY (sug_id),
     KEY sug_estado (sug_estado),
     KEY sug_user (sug_user)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

   CREATE TABLE IF NOT EXISTS w_sugerencias_votos (
     voto_id int(11) NOT NULL AUTO_INCREMENT,
     voto_sug int(11) NOT NULL DEFAULT 0,
     voto_user int(11) NOT NULL DEFAULT 0,
     voto_fecha int(11) NOT NULL DEFAULT 0,
     PRIMARY KEY (voto_id),
     UNIQUE KEY uniq_sug_user (voto_sug, voto_user)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

   -- Catalogo de Ventas
   CREATE TABLE IF NOT EXISTS w_catalogo_categorias (
     catcid int(11) NOT NULL AUTO_INCREMENT,
     catc_nombre varchar(100) NOT NULL DEFAULT '',
     catc_seo varchar(120) NOT NULL DEFAULT '',
     catc_orden int(11) NOT NULL DEFAULT 0,
     PRIMARY KEY (catcid)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

   CREATE TABLE IF NOT EXISTS w_catalogo (
     cat_id int(11) NOT NULL AUTO_INCREMENT,
     cat_user int(11) NOT NULL DEFAULT 0,
     cat_categoria int(11) NOT NULL DEFAULT 0,
     cat_titulo varchar(150) NOT NULL DEFAULT '',
     cat_descripcion text NULL,
     cat_precio decimal(10,2) NOT NULL DEFAULT 0.00,
     cat_condicion varchar(20) NOT NULL DEFAULT 'usado',
     cat_imagen varchar(255) NOT NULL DEFAULT '',
     cat_estado varchar(20) NOT NULL DEFAULT 'disponible',
     cat_destacado_hasta int(11) NOT NULL DEFAULT 0,
     cat_vistas int(11) NOT NULL DEFAULT 0,
     cat_fecha int(11) NOT NULL DEFAULT 0,
     PRIMARY KEY (cat_id),
     KEY cat_categoria (cat_categoria),
     KEY cat_user (cat_user),
     KEY cat_estado (cat_estado)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

   CREATE TABLE IF NOT EXISTS w_catalogo_imagenes (
     img_id int(11) NOT NULL AUTO_INCREMENT,
     img_cat int(11) NOT NULL DEFAULT 0,
     img_archivo varchar(255) NOT NULL DEFAULT '',
     img_orden int(11) NOT NULL DEFAULT 0,
     PRIMARY KEY (img_id),
     KEY img_cat (img_cat)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

   ALTER TABLE w_configuracion
     ADD COLUMN IF NOT EXISTS catalogo_activo tinyint(1) NOT NULL DEFAULT 0;

   ALTER TABLE w_configuracion
     ADD COLUMN IF NOT EXISTS catalogo_destacar_puntos int(11) NOT NULL DEFAULT 50;

4. Sube la carpeta inc/ext/phpmailer/ (3 archivos)
5. Sube sw.js y ajax.push.php a la raiz del sitio
6. Sube gif-thumb.php a la raiz del sitio
7. Sube modifier.gif_lazy.php a inc/smarty/plugins/
8. Crea la carpeta /files/gifcache/ con permisos de
   escritura (o deja que el script la cree solo)
9. Sustituye cron.php en la raiz del sitio (tiene 2
   tareas nuevas: limpieza de visitas y sesiones)
10. Borra las carpetas /cache/ y /templates_c/
11. Configura SMTP en Admin -> Configuracion
12. Configura Push en Admin -> Configuracion (opcional)
13. Configura Donaciones en Admin -> Configuracion (opcional)
14. Sube c.plugins.php a inc/class/ y m.admin_plugins.tpl
    a templates/admin_mods/ (ajusta la ruta segun tu tema)
15. Sustituye admin.php, .htaccess, t.admin.tpl y
    m.admin_sidemenu.tpl por los de esta actualizacion
    (o aplica manualmente el bloque de la seccion
    "plugins" si ya los tienes personalizados)
16. Sube c.notas_staff.php a inc/class/ y
    m.admin_notas.tpl a templates/admin_mods/, y anade
    el bloque de la seccion "notas" en admin.php,
    t.admin.tpl y m.admin_sidemenu.tpl (ver instrucciones
    del complemento "Notas de Staff" en el repositorio)
17. Sustituye c.posts.php por el de esta actualizacion
    (o aplica manualmente los cambios de savePost() y
    getLastPosts() si ya lo tienes personalizado)
18. Sustituye ajax.posts.php, m.agregar_form.tpl y
    m.posts_autor.tpl por los de esta actualizacion
    (Posts Conjuntos / coautoria)
19. Sustituye c.moderacion.php y ajax.moderacion.php
    por los de esta actualizacion, sube moderacion.js
    y p.moderacion_userlog.tpl a
    templates/t.php_files/, anade el boton "Ver log"
    en m.mod_report_users.tpl y m.mod_ban_users.tpl,
    y pega el bloque CSS en extras.css (Log de
    Moderacion de Usuario)
20. Sustituye c.posts.php, m.agregar_form.tpl y
    m.agregar_javascript.tpl por los de esta
    actualizacion, sube ajax.borradores.php a
    inc/php/ajax/ (NO a inc/php/ directo) y
    p.borradores.listar.tpl a templates/t.php_files/,
    y pega el bloque CSS en extras.css (Autoguardado
    de Borradores). No hace falta tocar .htaccess, ya
    enruta solo a traves de ajax_files.php
21. Sube c.sugerencias.php a inc/class/,
    sugerencias.php a inc/php/, ajax.sugerencias.php a
    inc/php/ajax/ (NO a inc/php/ directo), y
    sugerencias.tpl, m.sugerencias_listado.tpl y
    p.sugerencias.detalle.tpl a sus carpetas de
    templates correspondientes. Anade el icono de
    Sugerencias en head_menu.tpl. No hace falta tocar
    .htaccess, ya enruta solo a traves de
    ajax_files.php (Sugerencias de la Comunidad)
22. Sube c.catalogo.php a inc/class/, catalogo.php a
    inc/php/, ajax.catalogo.php a inc/php/ajax/, y
    catalogo.tpl, m.catalogo_listado.tpl,
    m.catalogo_mi.tpl y p.catalogo.detalle.tpl a sus
    carpetas de templates correspondientes. Anade el
    icono de Catalogo en head_menu.tpl y la seccion
    nueva en m.admin_configs.tpl. Activa el catalogo
    desde Admin -> Configuracion despues de correr el
    SQL (Catalogo de Ventas)
23. Sustituye head_menu.tpl por el de esta
    actualizacion (icono de Staff sin texto + menu de
    usuario agrupado en desplegable) (Menu Principal
    Mas Ordenado)
24. Si ya tenias ajax.borradores.php, ajax.sugerencias.php
    o ajax.catalogo.php de una actualizacion previa
    corriendo con errores raros de "clase no
    encontrada", sustituilos por los de esta
    actualizacion: se corrigio la ruta interna que
    usaban para cargar su clase (usan la constante
    TS_CLASS en vez de una ruta relativa que asumia
    mal la profundidad de carpeta)
=====================================================
  CREDITOS
=====================================================
PHPost Team | https://phpost.es/
Basado en T!Script por Tronlar
=====================================================
