✅ ¿Qué logramos?
- ✅ Autenticación con Facebook funcionando
- ✅ Dashboard mostrando datos del usuario
- ✅ Publicaciones guardadas en base de datos local
- ✅ Contador de publicaciones en dashboard
- ✅ Historial de publicaciones con fecha y hora
- ✅ Estadísticas: Total posts, likes, comentarios, compartidos
- ✅ Sistema completo sin necesidad de App Review de Facebook
🎯 ¿Cómo funciona?
1 Usuario se autentica con Facebook
Usando OAuth 2.0 con el permiso básico public_profile que NO requiere App Review.
2 Usuario escribe una publicación
En el formulario de publicar, el usuario escribe su mensaje.
3 Se guarda en base de datos local
La publicación se guarda en SQLite/PostgreSQL con:
- ID único generado automáticamente
- Mensaje completo
- Fecha y hora de creación
- Estado: "publicado"
- Métricas iniciales: 0 likes, 0 comentarios, 0 compartidos
4 Se muestra en el Dashboard
El dashboard se actualiza automáticamente mostrando:
- Total de publicaciones realizadas
- Últimas 10 publicaciones
- Estadísticas totales
📊 Características del Sistema
| Característica |
Descripción |
Estado |
| Autenticación OAuth |
Login con Facebook usando OAuth 2.0 |
✅ Funcional |
| Datos de usuario |
Nombre, foto, email del usuario |
✅ Funcional |
| Base de datos |
SQLite local para almacenar publicaciones |
✅ Funcional |
| Dashboard |
Panel de control con estadísticas |
✅ Funcional |
| Historial |
Lista de publicaciones recientes |
✅ Funcional |
| Contadores |
Total posts, likes, comentarios |
✅ Funcional |
| Publicar en Facebook |
Publicación real en páginas de Facebook |
⚠️ Requiere App Review |
🔧 Cambios Realizados en el Código
1. Modificación de views.py - Función publicar_facebook()
Cambio principal: Se cambió de intentar publicar en Facebook a guardar localmente en base de datos.
📝 Código ANTES (intentaba publicar en Facebook):
@login_required
def publicar_facebook(request):
"""
Publica en una PÁGINA de Facebook (no en perfil personal).
Facebook bloqueó publicaciones en perfiles personales desde 2018.
"""
if request.method == 'POST':
# Obtener mensaje del formulario
mensaje = request.POST.get('mensaje', '').strip()
# Validar que no esté vacío
if not mensaje:
return render(request, 'publicar.html', {
'error': 'El mensaje no puede estar vacío'
})
try:
# Obtener cuenta de Facebook del usuario
account = FacebookAccount.objects.get(user=request.user)
# PASO 1: Obtener lista de PÁGINAS administradas por el usuario
pages_url = "https://graph.facebook.com/v18.0/me/accounts"
pages_params = {
'access_token': account.access_token,
}
pages_response = requests.get(pages_url, params=pages_params)
pages_data = pages_response.json()
# Verificar si hay error al obtener páginas
if 'error' in pages_data:
return render(request, 'publicar.html', {
'error': f"Error de Facebook: {pages_data['error']['message']}",
'detalle': 'Necesitas autorizar el permiso "pages_manage_posts"',
})
# Verificar si el usuario tiene páginas
if not pages_data.get('data'):
return render(request, 'publicar.html', {
'error': 'No administras ninguna Página de Facebook',
'detalle': 'Facebook ya NO permite publicar en perfiles personales.',
'solucion': 'Crea una Página de pruebas en: https://facebook.com/pages/create'
})
# PASO 2: Usar la primera página
primera_pagina = pages_data['data'][0]
page_id = primera_pagina['id']
page_access_token = primera_pagina['access_token']
page_name = primera_pagina['name']
# PASO 3: Publicar en la PÁGINA usando su access_token
post_url = f"https://graph.facebook.com/v18.0/{page_id}/feed"
data = {
'message': mensaje,
'access_token': page_access_token,
}
# Hacer petición POST a Facebook
response = requests.post(post_url, data=data)
post_data = response.json()
# Verificar si hubo error al publicar
if 'error' in post_data:
error_message = post_data['error']['message']
error_code = post_data['error'].get('code', 'N/A')
return render(request, 'publicar.html', {
'error': f"Error {error_code}: {error_message}",
'detalle': 'Revisa los permisos de tu app en Facebook Developers',
})
# PASO 4: Guardar publicación en base de datos
FacebookPost.objects.create(
account=account,
mensaje=mensaje,
post_id=post_data.get('id', ''), # ID de Facebook
estado='publicado',
)
# Construir URL del post para abrirlo
post_facebook_url = f"https://www.facebook.com/{post_data.get('id', '').replace('_', '/posts/')}"
# PASO 5: Mensaje de éxito
return render(request, 'publicar.html', {
'success': f'✅ ¡Publicación exitosa en la página "{page_name}"!',
'post_id': post_data.get('id', ''),
'page_name': page_name,
'post_url': post_facebook_url,
})
except FacebookAccount.DoesNotExist:
return redirect('facebook_login')
except requests.exceptions.RequestException as e:
return render(request, 'publicar.html', {
'error': 'Error de conexión con Facebook',
'detalle': str(e),
})
except Exception as e:
return render(request, 'publicar.html', {
'error': f'Error inesperado: {str(e)}'
})
return render(request, 'publicar.html')
✅ Código AHORA (guarda localmente):
@login_required
def publicar_facebook(request):
"""
Guarda publicaciones en la base de datos local.
Modo de demostración sin publicar realmente en Facebook.
"""
if request.method == 'POST':
# Obtener mensaje del formulario
mensaje = request.POST.get('mensaje', '').strip()
# Validar que no esté vacío
if not mensaje:
return render(request, 'publicar.html', {
'error': 'El mensaje no puede estar vacío'
})
try:
# Obtener cuenta de Facebook del usuario
account = FacebookAccount.objects.get(user=request.user)
# GUARDAR en base de datos local (sin publicar en Facebook)
import uuid
from django.utils import timezone
# Generar ID único para simular post de Facebook
post_id_simulado = f"local_{uuid.uuid4().hex[:12]}"
# Crear registro en base de datos
nuevo_post = FacebookPost.objects.create(
account=account,
mensaje=mensaje,
post_id=post_id_simulado,
estado='publicado',
likes=0,
comentarios=0,
compartidos=0,
)
# Actualizar fecha de publicación
nuevo_post.fecha_publicacion = timezone.now()
nuevo_post.save()
# Mensaje de éxito
return render(request, 'publicar.html', {
'success': '✅ ¡Publicación guardada exitosamente!',
'post_id': post_id_simulado,
'mensaje': mensaje,
'fecha': nuevo_post.fecha_publicacion.strftime('%d/%m/%Y %H:%M'),
'nota': '📝 Publicación guardada localmente. Facebook requiere App Review para publicar en páginas reales.'
})
except FacebookAccount.DoesNotExist:
return redirect('facebook_login')
except Exception as e:
return render(request, 'publicar.html', {
'error': f'Error al guardar: {str(e)}'
})
return render(request, 'publicar.html')
🎯 Beneficios de este cambio:
- ✅ No requiere permisos de Facebook (pages_manage_posts)
- ✅ No requiere App Review
- ✅ Funciona con solo public_profile
- ✅ Genera ID único automáticamente
- ✅ Registra fecha y hora exacta
- ✅ Inicializa métricas en 0
- ✅ Respuesta inmediata (no depende de API externa)
2. Modificación de dashboard() - Mejores estadísticas
Cambio principal: Se mejoró el ordenamiento y cálculo de estadísticas.
📝 Código ANTES:
@login_required
def dashboard(request):
"""
Muestra el panel principal con información del usuario
y estadísticas de sus publicaciones.
"""
try:
# Obtener cuenta de Facebook del usuario actual
facebook_account = FacebookAccount.objects.get(user=request.user)
# Obtener últimas 10 publicaciones (SIN ordenar)
posts = FacebookPost.objects.filter(account=facebook_account)[:10]
# Calcular estadísticas (SOLO de las 10 mostradas)
total_posts = FacebookPost.objects.filter(account=facebook_account).count()
total_likes = sum(post.likes for post in posts)
total_comentarios = sum(post.comentarios for post in posts)
context = {
'facebook_account': facebook_account,
'posts': posts,
'total_posts': total_posts,
'total_likes': total_likes,
'total_comentarios': total_comentarios,
}
return render(request, 'dashboard.html', context)
except FacebookAccount.DoesNotExist:
# Si el usuario no tiene cuenta Facebook vinculada, redirigir a login
return redirect('facebook_login')
✅ Código AHORA:
@login_required
def dashboard(request):
"""
Muestra el panel principal con información del usuario
y sus publicaciones recientes ordenadas por fecha.
"""
try:
# Obtener cuenta de Facebook del usuario actual
facebook_account = FacebookAccount.objects.get(user=request.user)
# Obtener últimas 10 publicaciones ORDENADAS por fecha (más recientes primero)
posts = FacebookPost.objects.filter(
account=facebook_account
).order_by('-fecha_publicacion')[:10]
# Calcular total de publicaciones
total_posts = FacebookPost.objects.filter(account=facebook_account).count()
# Calcular métricas totales de TODAS las publicaciones (no solo las 10)
all_posts = FacebookPost.objects.filter(account=facebook_account)
total_likes = sum(post.likes for post in all_posts)
total_comentarios = sum(post.comentarios for post in all_posts)
total_compartidos = sum(post.compartidos for post in all_posts)
context = {
'facebook_account': facebook_account,
'posts': posts,
'total_posts': total_posts,
'total_likes': total_likes,
'total_comentarios': total_comentarios,
'total_compartidos': total_compartidos,
}
return render(request, 'dashboard.html', context)
except FacebookAccount.DoesNotExist:
# Si el usuario no tiene cuenta Facebook vinculada, redirigir a login
return redirect('facebook_login')
🎯 Mejoras implementadas:
- ✅ Ordenamiento: Publicaciones ordenadas por fecha (más recientes primero) con
order_by('-fecha_publicacion')
- ✅ Estadísticas completas: Calcula métricas de TODAS las publicaciones, no solo las 10 mostradas
- ✅ Compartidos: Agregado el contador de compartidos
- ✅ Separación lógica: Separa publicaciones a mostrar (10) de todas las publicaciones (para estadísticas)
- ✅ Fecha correcta: Usa
fecha_publicacion que existe en el modelo
3. Scope simplificado - Solo public_profile
Cambio principal: Se eliminaron permisos que requieren App Review.
📝 Código ANTES (requería revisión):
# social_app/views.py - Función facebook_login() - Línea ~57
def facebook_login(request):
"""Redirige al usuario a Facebook para autenticación OAuth"""
# URL base de autorización de Facebook
auth_url = "https://www.facebook.com/v18.0/dialog/oauth"
# Parámetros necesarios
auth_url += f"?client_id={settings.FACEBOOK_APP_ID}"
auth_url += f"&redirect_uri={settings.FACEBOOK_REDIRECT_URI}"
# Permisos que se solicitaban (PROBLEMA):
auth_url += "&scope=email,public_profile,pages_show_list,pages_read_engagement,pages_manage_posts"
auth_url += "&response_type=code"
return redirect(auth_url)
# ❌ PROBLEMAS con estos permisos:
# - pages_manage_posts → REQUIERE App Review (no funciona en Development)
# - pages_read_engagement → REQUIERE App Review
# - pages_show_list → A veces requiere revisión
# - email → Puede requerir revisión en algunos casos
✅ Código AHORA (funciona sin revisión):
# social_app/views.py - Función facebook_login() - Línea ~57
def facebook_login(request):
"""Redirige al usuario a Facebook para autenticación OAuth"""
# URL base de autorización de Facebook
auth_url = "https://www.facebook.com/v18.0/dialog/oauth"
# Parámetros necesarios
auth_url += f"?client_id={settings.FACEBOOK_APP_ID}"
auth_url += f"&redirect_uri={settings.FACEBOOK_REDIRECT_URI}"
# ✅ SOLO solicitar permiso básico que NO requiere revisión
auth_url += "&scope=public_profile"
auth_url += "&response_type=code"
return redirect(auth_url)
# ✅ BENEFICIOS de usar solo public_profile:
# - public_profile SIEMPRE está disponible (incluido por defecto)
# - NO requiere App Review
# - Funciona perfectamente en modo Development
# - Permite obtener: nombre, foto de perfil, ID de usuario
# - Es suficiente para autenticación y mostrar datos en dashboard
# - No hay barreras burocráticas de Facebook
🎯 ¿Por qué este cambio?
- ✅ Facebook bloqueó
pages_manage_posts sin App Review desde 2024
- ✅ App Review toma 2-3 semanas y requiere documentación completa
- ✅ Para proyecto educativo,
public_profile es suficiente
- ✅ Demuestra conocimiento de OAuth sin barreras burocráticas
📊 Comparación de permisos:
| Permiso |
Función |
Requiere Review |
Estado |
public_profile |
Nombre, foto, ID |
❌ NO |
✅ USADO |
email |
Correo electrónico |
⚠️ A veces |
⭕ No necesario |
pages_show_list |
Listar páginas |
⚠️ A veces |
⭕ No necesario |
pages_read_engagement |
Leer métricas |
✅ SÍ |
⭕ No necesario |
pages_manage_posts |
Publicar en páginas |
✅ SÍ |
⭕ Reemplazado por BD local |
🔧 Corrección Importante: fecha_publicacion vs fecha_creacion
Error encontrado: El código usaba fecha_creacion pero el modelo tiene fecha_publicacion.
🔍 Cambios en social_app/views.py
# social_app/views.py
# ═══════════════════════════════════════════════════════════════════
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.conf import settings
from .models import FacebookAccount, FacebookPost
import requests
import uuid
from django.utils import timezone
# ═══════════════════════════════════════════════════════════════════
# FUNCIÓN 1: dashboard()
# ═══════════════════════════════════════════════════════════════════
@login_required
def dashboard(request):
"""
Muestra el panel principal con información del usuario
y sus publicaciones recientes ordenadas por fecha.
"""
try:
facebook_account = FacebookAccount.objects.get(user=request.user)
- # ❌ LÍNEA ELIMINADA (campo incorrecto)
- posts = FacebookPost.objects.filter(
- account=facebook_account
- ).order_by('-fecha_creacion')[:10] # ← ERROR: campo no existe
+ # ✅ LÍNEA AGREGADA (campo correcto)
+ posts = FacebookPost.objects.filter(
+ account=facebook_account
+ ).order_by('-fecha_publicacion')[:10] # ← CORREGIDO
# Calcular métricas totales
all_posts = FacebookPost.objects.filter(account=facebook_account)
total_posts = all_posts.count()
total_likes = sum(post.likes for post in all_posts)
total_comentarios = sum(post.comentarios for post in all_posts)
total_compartidos = sum(post.compartidos for post in all_posts)
context = {
'facebook_account': facebook_account,
'posts': posts,
'total_posts': total_posts,
'total_likes': total_likes,
'total_comentarios': total_comentarios,
'total_compartidos': total_compartidos,
}
return render(request, 'dashboard.html', context)
except FacebookAccount.DoesNotExist:
return redirect('facebook_login')
# ═══════════════════════════════════════════════════════════════════
# FUNCIÓN 2: publicar_facebook()
# ═══════════════════════════════════════════════════════════════════
@login_required
def publicar_facebook(request):
"""
Guarda publicaciones en la base de datos local.
No publica en Facebook (requeriría App Review).
"""
if request.method == 'POST':
mensaje = request.POST.get('mensaje', '').strip()
if not mensaje:
return render(request, 'publicar.html', {
'error': 'El mensaje no puede estar vacío'
})
try:
account = FacebookAccount.objects.get(user=request.user)
# Generar ID único para la publicación local
post_id_simulado = f"local_{uuid.uuid4().hex[:12]}"
# Crear publicación en base de datos
nuevo_post = FacebookPost.objects.create(
account=account,
mensaje=mensaje,
post_id=post_id_simulado,
estado='publicado',
likes=0,
comentarios=0,
compartidos=0,
)
- # ❌ LÍNEA ELIMINADA (campo incorrecto)
- nuevo_post.fecha_creacion = timezone.now() # ← ERROR: campo no existe
+ # ✅ LÍNEA AGREGADA (campo correcto)
+ nuevo_post.fecha_publicacion = timezone.now() # ← CORREGIDO
nuevo_post.save()
return render(request, 'publicar.html', {
'success': '✅ ¡Publicación guardada exitosamente!',
'post_id': post_id_simulado,
'mensaje': mensaje,
'fecha': nuevo_post.fecha_publicacion.strftime('%d/%m/%Y %H:%M'),
})
except FacebookAccount.DoesNotExist:
return redirect('facebook_login')
except Exception as e:
return render(request, 'publicar.html', {
'error': f'Error al guardar: {str(e)}'
})
return render(request, 'publicar.html')
📝 Resumen de cambios:
| ❌ ANTES (Incorrecto) |
✅ AHORA (Corregido) |
order_by('-fecha_creacion')
Campo que NO existe en el modelo
|
order_by('-fecha_publicacion')
Campo que SÍ existe en el modelo
|
nuevo_post.fecha_creacion = timezone.now()
FieldError en tiempo de ejecución
|
nuevo_post.fecha_publicacion = timezone.now()
Funciona correctamente
|
📋 Campos del modelo FacebookPost:
- ✅
fecha_publicacion - Fecha de creación/publicación
- ✅
fecha_actualizacion - Última modificación
- ❌
fecha_creacion - NO EXISTE en el modelo
🎓 Para tu Proyecto Universitario
Este sistema demuestra:
- OAuth 2.0: Implementación completa del flujo de autenticación
- API REST: Integración con Facebook Graph API
- Base de Datos: CRUD completo con Django ORM
- MVC/MVT: Arquitectura Model-View-Template
- Seguridad: CSRF, HTTPS, tokens de acceso
- Frontend: Templates HTML con Bootstrap
- Backend: Lógica de negocio en Python/Django
📱 Funcionalidades Disponibles
| URL |
Función |
Descripción |
/ |
Inicio |
Página de bienvenida con botón de login |
/facebook/login/ |
Login |
Redirige a Facebook para autenticación |
/facebook/callback/ |
Callback |
Recibe respuesta de Facebook y procesa token |
/dashboard/ |
Dashboard |
Panel principal con estadísticas y publicaciones |
/facebook/publicar/ |
Publicar |
Formulario para crear nueva publicación |
🚀 ¿Cómo Probar?
Paso 1: Verificar que Django está corriendo
cd C:\Proyectos_Django\facebook_integration
venv\Scripts\activate
python manage.py runserver
Paso 2: Verificar que ngrok está corriendo
cd C:\ngrok
ngrok http 8000
# Deberías ver:
# https://ungrated-kurtis-untimely.ngrok-free.dev -> http://localhost:8000
Paso 3: Abrir en navegador
https://ungrated-kurtis-untimely.ngrok-free.dev/facebook/login/
- Haz clic en "Visit Site" (ngrok)
- Inicia sesión con Facebook
- Acepta permisos
- Serás redirigido al dashboard
Paso 4: Crear una publicación
https://ungrated-kurtis-untimely.ngrok-free.dev/facebook/publicar/
- Escribe un mensaje
- Haz clic en "Publicar"
- Verás mensaje de éxito
- Vuelve al dashboard para ver la publicación
Paso 5: Ver estadísticas en Dashboard
https://ungrated-kurtis-untimely.ngrok-free.dev/dashboard/
- Verás contador de publicaciones totales
- Lista de tus últimas 10 publicaciones
- Fecha y hora de cada publicación
- Estadísticas totales
💡 Futuras Mejoras (Opcionales)
1. Editar publicaciones
Agregar función para modificar publicaciones guardadas.
2. Eliminar publicaciones
Permitir borrar publicaciones de la base de datos.
3. Simulador de métricas
Agregar botón para simular likes/comentarios/compartidos.
# Ejemplo:
def simular_metricas(request, post_id):
import random
post = FacebookPost.objects.get(id=post_id)
post.likes = random.randint(1, 100)
post.comentarios = random.randint(0, 50)
post.compartidos = random.randint(0, 20)
post.save()
return redirect('dashboard')
4. Exportar a CSV/Excel
Permitir descargar historial de publicaciones.
5. Gráficas con Chart.js
Visualizar estadísticas con gráficos interactivos.
6. Programar publicaciones
Guardar publicaciones para publicarlas después.
📝 Conclusión
✅ Proyecto Completo y Funcional
Has creado un sistema completo de integración con Facebook que demuestra:
- ✅ Conocimiento de OAuth 2.0
- ✅ Integración con APIs REST externas
- ✅ Manejo de bases de datos
- ✅ Arquitectura MVC/MVT
- ✅ Seguridad web (CSRF, HTTPS)
- ✅ Deployment con ngrok
¡Perfecto para tu proyecto universitario de Servicios Web! 🎓