Skip to content

Commit fff955a

Browse files
unas mejoras cuantas, sisttema de puntuacion/voto en implementacion
1 parent cb02a06 commit fff955a

8 files changed

Lines changed: 236 additions & 66 deletions

File tree

app/controllers/comentarios_controller.rb

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@ class ComentariosController < ApplicationController
33

44
def create
55
@preguntum = Preguntum.find(params[:preguntum_id])
6-
# Creamos el comentario asociado a la pregunta y al usuario actual
76
@comentario = @preguntum.comentarios.new(cuerpo: params[:cuerpo])
87
@comentario.usuario_id = usuario_actual.id
9-
# Si viene de una respuesta, le asignamos su padre
108
@comentario.comentario_padre_id = params[:comentario_padre_id] if params[:comentario_padre_id].present?
119

1210
if @comentario.save
@@ -16,6 +14,35 @@ def create
1614
end
1715
end
1816

17+
# Acción para procesar la actualización del comentario
18+
def update
19+
@comentario = Comentario.find(params[:id])
20+
21+
# Seguridad: Solo el autor real puede editar su propio comentario no-fantasma
22+
if usuario_actual && @comentario.usuario_id == usuario_actual.id && !@comentario.fantasma?
23+
if @comentario.update(cuerpo: params[:cuerpo])
24+
redirect_to @comentario.preguntum, notice: "¡Comentario actualizado!"
25+
else
26+
redirect_to @comentario.preguntum, alert: "Error al actualizar el comentario."
27+
end
28+
else
29+
redirect_to @comentario.preguntum, alert: "Acceso denegado."
30+
end
31+
end
32+
33+
def destroy
34+
@comentario = Comentario.find(params[:id])
35+
36+
if usuario_actual && @comentario.usuario_id == usuario_actual.id
37+
# Al ejecutar este método, se disparan los descuentos de votos y la poda de fantasmas de forma secuencial
38+
@comentario.eliminar_con_poda!
39+
redirect_to preguntum_path(@comentario.preguntum), notice: "El comentario y sus puntuaciones asociadas han sido procesados."
40+
else
41+
redirect_to preguntum_path(@comentario.preguntum), alert: "Acceso denegado."
42+
end
43+
end
44+
45+
1946
def votar_arriba
2047
@comentario = Comentario.find(params[:id])
2148
@comentario.increment!(:votos)

app/controllers/pregunta_controller.rb

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -108,28 +108,54 @@ def preguntum_params
108108
params.require(:preguntum).permit(:titulo, :cuerpo)
109109
end
110110

111-
def procesar_voto(tipo)
112-
# 1. Regla: No autovotos
111+
def procesar_voto(tipo_deseado)
112+
# 1. Regla de oro: No autovotos
113113
if @preguntum.usuario_id == usuario_actual.id
114114
return redirect_to root_path, alert: "¡No puedes votar tus propias preguntas!"
115115
end
116116

117-
# 2. Regla: Máximo 10 votos POR PREGUNTA por persona
118-
votos_usuario_en_esta_pregunta = Voto.where(usuario_id: usuario_actual.id, preguntum_id: @preguntum.id).count
119-
120-
if votos_usuario_en_esta_pregunta >= 10
121-
return redirect_to root_path, alert: "Ya alcanzaste el límite de 10 votos en esta pregunta."
122-
end
123-
124-
# Si pasa las reglas, se registra el voto
125-
Voto.create(usuario_id: usuario_actual.id, preguntum_id: @preguntum.id, tipo: tipo)
126-
127-
if tipo == "arriba"
128-
@preguntum.increment!(:votos)
117+
# 2. Buscamos si este usuario ya tiene un voto registrado en esta pregunta
118+
voto_existente = Voto.find_by(usuario_id: usuario_actual.id, preguntum_id: @preguntum.id)
119+
120+
if voto_existente
121+
if voto_existente.tipo == tipo_deseado
122+
# CASO A: Presionó el mismo botón que ya tenía activo -> Cancela el voto (Reddit Neutral)
123+
voto_existente.destroy
124+
125+
if tipo_deseado == "arriba"
126+
@preguntum.decrement!(:votos) # Baja 1 punto
127+
else
128+
@preguntum.increment!(:votos) # Recupera 1 punto del negativo
129+
end
130+
131+
flash[:notice] = "Voto retirado."
132+
else
133+
# CASO B: Cambió de bando (Estaba arriba y presionó abajo, o viceversa)
134+
voto_existente.update(tipo: tipo_deseado)
135+
136+
if tipo_deseado == "arriba"
137+
# Pasó de abajo (-1) a arriba (+1) -> Salto neto de +2
138+
@preguntum.update(votos: @preguntum.votos + 2)
139+
else
140+
# Pasó de arriba (+1) a abajo (-1) -> Salto neto de -2
141+
@preguntum.update(votos: @preguntum.votos - 2)
142+
end
143+
144+
flash[:notice] = "Voto modificado."
145+
end
129146
else
130-
@preguntum.decrement!(:votos)
147+
# CASO C: Primer voto del usuario en esta publicación (Estaba neutral)
148+
Voto.create(usuario_id: usuario_actual.id, preguntum_id: @preguntum.id, tipo: tipo_deseado)
149+
150+
if tipo_deseado == "arriba"
151+
@preguntum.increment!(:votos)
152+
else
153+
@preguntum.decrement!(:votos)
154+
end
155+
156+
flash[:notice] = "¡Voto registrado!"
131157
end
132158

133-
redirect_to root_path, notice: "¡Voto registrado!"
159+
redirect_to root_path
134160
end
135161
end

app/controllers/usuarios_controller.rb

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -91,29 +91,46 @@ def edit
9191

9292
def update
9393
@usuario = usuario_actual
94-
95-
# Procesar foto Base64 (tu lógica facherina existente)
96-
if params[:usuario][:foto].present?
97-
archivo = params[:usuario][:foto].tempfile
94+
@foto_previa_base64 = params[:foto_temporal_base64] # Recuperamos si ya existía una preview previa
95+
96+
# 1. Procesar la foto Base64 SÓLO si realmente hay un archivo en los params
97+
# Usamos .try(:[], :foto) para evitar errores si el hash de usuario viene vacío por alguna razón
98+
foto_param = params[:usuario].try(:[], :foto)
99+
100+
if foto_param.present?
101+
archivo = foto_param.tempfile
98102
base64_data = Base64.strict_encode64(archivo.read)
99-
@usuario.foto_base64 = "data:#{params[:usuario][:foto].content_type};base64,#{base64_data}"
103+
@foto_previa_base64 = "data:#{foto_param.content_type};base64,#{base64_data}"
100104
end
101105

102-
# Intentamos asignar los nuevos datos
106+
# Asignamos los datos de texto en memoria para que no se borren en el render
103107
@usuario.nombre = params[:usuario][:nombre]
104108
@usuario.descripcion = params[:usuario][:descripcion]
105109

106-
# Si pasa las validaciones (nombre único), guarda y redirige
107-
if @usuario.save
108-
# OJO: Si tu sistema de cookies de sesión usa el NOMBRE del usuario para trackearlo,
109-
# actualiza la cookie aquí para que no pierda la sesión activa:
110-
cookies.encrypted[:usuario_id] = @usuario.id # RECOMENDADO: Guardar el ID, nunca el nombre mero
110+
# 2. EVALUAR QUÉ BOTÓN SE PRESIONÓ
111+
if params[:previsualizar].present?
112+
# BUG CORREGIDO: Si presionó previsualizar pero no subió nada nuevo ni había nada guardado antes
113+
if foto_param.blank? && params[:foto_temporal_base64].blank?
114+
flash.now[:alert] = "No has seleccionado ninguna imagen para previsualizar."
115+
else
116+
flash.now[:notice] = "Vista previa cargada. Recuerda confirmar los cambios."
117+
end
111118

112-
redirect_to usuario_path(@usuario), notice: "¡Perfil facherito actualizado con éxito!"
113-
else
114-
# Si el nombre está repetido, Rails mete el error en @usuario.errors
115-
flash.now[:alert] = "No se pudo actualizar: El nombre de usuario ya está en uso por alguien más."
116119
render :edit, status: :unprocessable_entity
120+
121+
elsif params[:guardar].present?
122+
# Si había una foto en la preview (ya sea de ahora o del intento anterior), la consolidamos
123+
if @foto_previa_base64.present?
124+
@usuario.foto_base64 = @foto_previa_base64
125+
end
126+
127+
if @usuario.save
128+
cookies.encrypted[:usuario_id] = @usuario.id
129+
redirect_to usuario_path(@usuario), notice: "¡Perfil facherito actualizado con éxito!"
130+
else
131+
flash.now[:alert] = "No se pudo actualizar: El nombre de usuario ya está en uso por alguien más."
132+
render :edit, status: :unprocessable_entity
133+
end
117134
end
118135
end
119136
end

app/models/comentario.rb

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,69 @@
11
class Comentario < ApplicationRecord
2-
belongs_to :usuario
2+
belongs_to :usuario, optional: true
33
belongs_to :preguntum
44

5-
# La magia auto-referencial:
65
belongs_to :padre, class_name: "Comentario", foreign_key: "comentario_padre_id", optional: true
76
has_many :hijos, class_name: "Comentario", foreign_key: "comentario_padre_id", dependent: :destroy
87

8+
# Relación con la tabla física de votos (asumiendo que tu modelo se llama Voto)
9+
# Si tu tabla de votos guarda el comentario_id, se destruyen en cascada
10+
11+
912
validates :cuerpo, presence: true
1013
before_create :poner_votos_en_cero
14+
15+
# CALLBACKS: Limpiar estadísticas del autor antes de la destrucción física total
16+
before_destroy :limpiar_votos_del_autor
17+
18+
def eliminar_con_poda!
19+
# 1. Antes de alterar el comentario, le restamos sus votos acumulados al autor
20+
descontar_votos_del_perfil(self.votos) if self.usuario.present?
21+
22+
# 2. Procedemos con la bifurcación del algoritmo de poda
23+
if hijos.any?
24+
# Soft-delete: Pasa a ser fantasma, pierde sus votos y se desvincula del autor
25+
update(cuerpo: "[Este comentario fue eliminado]", usuario_id: nil, votos: 0)
26+
# Limpiamos físicamente los registros de la tabla de votos para este comentario
27+
registro_votos.destroy_all
28+
else
29+
# Purga física instantánea si no tiene descendencia
30+
destroy
31+
end
32+
end
33+
34+
def fantasma?
35+
usuario_id.nil? && cuerpo == "[Este comentario fue eliminado]"
36+
end
1137

1238
private
39+
1340
def poner_votos_en_cero
1441
self.votos = 0
1542
end
43+
44+
# Limpieza para cuando el comentario se destruye físicamente de la BD
45+
def limpiar_votos_del_autor
46+
return unless usuario.present?
47+
descontar_votos_del_perfil(self.votos)
48+
end
49+
50+
# Método auxiliar para actualizar el contador del perfil del Usuario de forma masiva
51+
def descontar_votos_del_perfil(cantidad_votos)
52+
return if cantidad_votos == 0
53+
54+
# Verificamos si tu modelo Usuario tiene una columna para acumular la reputación/votos recibidos
55+
if Usuario.column_names.include?('votos_totales')
56+
usuario.decrement!(:votos_totales, cantidad_votos)
57+
elsif Usuario.column_names.include?('reputacion')
58+
usuario.decrement!(:reputacion, cantidad_votos)
59+
end
60+
end
61+
62+
# Recolector en cascada reversa: Limpia padres fantasmas vacíos
63+
def recolector_fantasmas_reverso
64+
return unless padre.present?
65+
if padre.fantasma? && padre.hijos.empty?
66+
padre.destroy
67+
end
68+
end
1669
end

app/models/preguntum.rb

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,34 @@
11
class Preguntum < ApplicationRecord
2-
belongs_to :usuario, optional: true
2+
belongs_to :usuario
33

4-
# ¡ESTA ES LA LÍNEA QUE FALTA!
4+
# MODIFICADO: Al borrar la pregunta se eliminan todos sus comentarios hilos/hijos
55
has_many :comentarios, dependent: :destroy
6+
7+
# MODIFICADO: Cambiamos el nombre a :registro_votos para no chocar con el entero :votos
8+
has_many :registro_votos, class_name: 'Voto', dependent: :destroy
69

710
validates :titulo, :cuerpo, presence: true
811
before_create :poner_votos_en_cero
12+
13+
# Callback para ajustar las estadísticas de los usuarios antes de borrar
14+
before_destroy :descontar_votos_a_usuarios
915

1016
private
17+
1118
def poner_votos_en_cero
1219
self.votos = 0
1320
end
14-
end
21+
22+
# Lógica para limpiar el historial de votación de los usuarios
23+
def descontar_votos_a_usuarios
24+
# Ahora iteramos sobre la relación usando el nuevo nombre
25+
self.registro_votos.each do |voto|
26+
usuario_que_voto = voto.usuario
27+
next unless usuario_que_voto.present?
28+
29+
if usuario_que_voto.respond_to?(:votos_totales)
30+
usuario_que_voto.decrement!(:votos_totales)
31+
end
32+
end
33+
end
34+
end

app/models/usuario.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
require 'bcrypt'
22

33
class Usuario < ApplicationRecord
4-
has_many :pregunta
5-
has_many :comentarios
4+
# MODIFICADO: Se añade dependent: :destroy a ambas relaciones
5+
has_many :pregunta, dependent: :destroy
6+
has_many :comentarios, dependent: :destroy
67

78
validates :nombre, presence: true, uniqueness: { case_sensitive: false }, length: { minimum: 3 }
89
validates :contrasena, presence: true, length: { minimum: 6 }
@@ -23,4 +24,4 @@ def contrasena_valida?(password_ingresado)
2324
def encriptar_contrasena
2425
self.contrasena = BCrypt::Password.create(self.contrasena)
2526
end
26-
end
27+
end
Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,57 @@
11
<div style="margin-left: 20px; border-left: 2px solid var(--border-color); padding-left: 15px; margin-top: 12px; margin-bottom: 12px;">
22
<p style="margin: 0; font-size: 0.85em; color: var(--text-secundario); display: flex; align-items: center; gap: 6px;">
3-
<% if comentario.usuario.foto_base64.present? %>
4-
<img src="<%= comentario.usuario.foto_base64 %>" style="width: 16px; height: 16px; border-radius: 50%; object-fit: cover;">
3+
<% if comentario.fantasma? %>
4+
<span>?</span>
5+
<strong style="color: var(--text-secundario);">Anonimo</strong>
56
<% else %>
6-
<span>👤</span>
7+
<% if comentario.usuario&.foto_base64.present? %>
8+
<img src="<%= comentario.usuario.foto_base64 %>" style="width: 16px; height: 16px; border-radius: 50%; object-fit: cover;">
9+
<% else %>
10+
<span>👤</span>
11+
<% end %>
12+
<strong style="color: #fff;"><%= comentario.usuario&.nombre || "Anónimo" %></strong>
713
<% end %>
8-
<strong style="color: #fff;"><%= comentario.usuario.nombre %></strong> | Votos: <span style="color: var(--accent); font-weight: bold;"><%= comentario.votos %></span>
14+
| Votos: <span style="color: var(--accent); font-weight: bold;"><%= comentario.votos %></span>
915
</p>
1016

11-
<p style="margin: 6px 0; color: var(--text-principal); font-size: 0.95em; white-space: pre-line;"><%= comentario.cuerpo %></p>
17+
<p style="margin: 6px 0; color: var(--text-principal); font-size: 0.95em; white-space: pre-line; <%= 'font-style: italic; color: var(--text-secundario);' if comentario.fantasma? %>"><%= comentario.cuerpo %></p>
1218

1319
<div style="display: flex; gap: 12px; font-size: 0.8em; align-items: center;">
14-
<%= link_to "▲", votar_arriba_preguntum_comentario_path(comentario.preguntum, comentario), method: :patch, style: "color: var(--accent); font-weight: bold;" %>
15-
<%= link_to "▼", votar_abajo_preguntum_comentario_path(comentario.preguntum, comentario), method: :patch, style: "color: #da3633; font-weight: bold;" %>
20+
<%= button_to "▲", votar_arriba_preguntum_comentario_path(comentario.preguntum, comentario), method: :patch, style: "background: none; border: none; cursor: pointer; color: var(--accent); font-weight: bold; padding: 0;" %>
21+
<%= button_to "▼", votar_abajo_preguntum_comentario_path(comentario.preguntum, comentario), method: :patch, style: "background: none; border: none; cursor: pointer; color: #da3633; font-weight: bold; padding: 0;" %>
1622

17-
<details style="display: inline;">
18-
<summary style="cursor: pointer; color: var(--text-secundario); list-style: none;">Responder</summary>
19-
<%= form_with url: preguntum_comentarios_path(comentario.preguntum), local: true, style: "margin-top: 8px; min-width: 250px;" do |f| %>
20-
<%= hidden_field_tag :comentario_padre_id, comentario.id %>
21-
<%= f.text_area :cuerpo, rows: 2, placeholder: "Escribe tu respuesta pública...", required: true, style: "margin-bottom: 8px;" %><br>
22-
<%= f.submit "Responder", style: "padding: 6px 12px; font-size: 0.85em;" %>
23+
<% unless comentario.fantasma? %>
24+
<details style="display: inline;">
25+
<summary style="cursor: pointer; color: var(--text-secundario); list-style: none;">Responder</summary>
26+
<%= form_with url: preguntum_comentarios_path(comentario.preguntum), local: true, style: "margin-top: 8px; min-width: 250px;" do |f| %>
27+
<%= hidden_field_tag :comentario_padre_id, comentario.id %>
28+
<%= f.text_area :cuerpo, rows: 2, placeholder: "Escribe tu respuesta pública...", required: true, style: "margin-bottom: 8px;" %><br>
29+
<%= f.submit "Responder", style: "padding: 6px 12px; font-size: 0.85em;" %>
30+
<% end %>
31+
</details>
32+
33+
<%# CONTROL DE ACCIONES DE AUTOR: Editar y Borrar %>
34+
<% if usuario_actual && comentario.usuario_id == usuario_actual.id %>
35+
<span style="color: var(--border-color);">|</span>
36+
37+
<%# Formulario Inline para editar usando el array polimórfico [pregunta, comentario] %>
38+
<details style="display: inline;">
39+
<summary style="cursor: pointer; color: var(--accent); list-style: none;">Editar</summary>
40+
<%# SOLUCIÓN POLIMÓRFICA: Rails deduce la ruta automáticamente %>
41+
<%= form_with model: [comentario.preguntum, comentario], method: :patch, local: true, style: "margin-top: 8px; min-width: 250px;" do |f| %>
42+
<%= f.text_area :cuerpo, value: comentario.cuerpo, rows: 2, required: true, style: "margin-bottom: 8px;" %><br>
43+
<%= f.submit "Guardar Cambios", style: "padding: 6px 12px; font-size: 0.85em;" %>
44+
<% end %>
45+
</details>
46+
47+
<span style="color: var(--border-color);">|</span>
48+
<%# Botón para borrar usando la misma solución polimórfica %>
49+
<%= button_to "Borrar", [comentario.preguntum, comentario], method: :delete, data: { confirm: "¿Seguro que deseas eliminar este comentario?" }, style: "background: none; border: none; cursor: pointer; color: #da3633; padding: 0; font-family: inherit; font-size: inherit;" %>
2350
<% end %>
24-
</details>
51+
<% end %>
2552
</div>
2653

2754
<% if comentario.hijos.any? %>
2855
<%= render partial: 'comentarios/comentario', collection: comentario.hijos.order(votos: :desc, created_at: :asc) %>
2956
<% end %>
30-
</div>
57+
</div>

0 commit comments

Comments
 (0)