📝 Sistema de Publicaciones Local

Guarda y contabiliza publicaciones sin necesidad de App Review

✅ ¿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! 🎓