blog+contacto php listo

This commit is contained in:
2026-09-20 13:28:57 -03:00
parent cd67fca3dc
commit cfb97b486e
24 changed files with 8970 additions and 165 deletions
+19
View File
@@ -0,0 +1,19 @@
<?php
require_once '../config/db.php';
// Verificar si el usuario está logueado
function verificarAuth() {
if (!isset($_SESSION['admin_id'])) {
header('Location: login.php');
exit;
}
}
// Obtener datos del admin logueado
function getAdminActual() {
global $pdo;
$stmt = $pdo->prepare("SELECT id, nombre, email FROM admin_users WHERE id = ?");
$stmt->execute([$_SESSION['admin_id']]);
return $stmt->fetch();
}
?>
+176
View File
@@ -0,0 +1,176 @@
<?php
require_once '../config/db.php';
require_once 'auth.php';
verificarAuth();
// Obtener todos los artículos
$stmt = $pdo->prepare("
SELECT p.*, u.nombre as autor_nombre
FROM blog_posts p
LEFT JOIN admin_users u ON p.autor_id = u.id
ORDER BY p.created_at DESC
");
$stmt->execute();
$posts = $stmt->fetchAll();
$admin = getAdminActual();
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard - Blog Amor y Roma</title>
<link rel="stylesheet" href="../style/main.css">
<style>
.admin-header {
background: white;
padding: 1.5rem 2rem;
border-bottom: 1px solid rgba(0, 63, 82, 0.12);
display: flex;
justify-content: space-between;
align-items: center;
}
.admin-container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
.btn-nuevo {
background: #ba124d;
color: white;
padding: 0.75rem 1.5rem;
border-radius: 24px;
text-decoration: none;
font-weight: 600;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-nuevo:hover {
background: #9c0f40;
}
.posts-table {
width: 100%;
border-collapse: collapse;
margin-top: 2rem;
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
}
.posts-table th,
.posts-table td {
padding: 1rem;
text-align: left;
border-bottom: 1px solid rgba(0, 63, 82, 0.12);
}
.posts-table th {
background: #f5f9f8;
font-weight: 600;
color: #003f52;
font-size: 0.85rem;
text-transform: uppercase;
}
.posts-table tr:hover {
background: #f9f9f9;
}
.estado-badge {
padding: 0.25rem 0.75rem;
border-radius: 999px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.estado-publicado {
background: #d4edda;
color: #155724;
}
.estado-borrador {
background: #fff3cd;
color: #856404;
}
.acciones {
display: flex;
gap: 0.5rem;
}
.btn-accion {
padding: 0.4rem 0.8rem;
border-radius: 6px;
text-decoration: none;
font-size: 0.85rem;
font-weight: 600;
}
.btn-editar {
background: #003f52;
color: white;
}
.btn-eliminar {
background: #dc3545;
color: white;
}
.logout-link {
color: #ba124d;
text-decoration: none;
font-weight: 600;
}
</style>
</head>
<body>
<div class="admin-header">
<div>
<h2 style="margin: 0; font-family: 'Literata', serif;">Hola, <?= htmlspecialchars($admin['nombre']) ?></h2>
<p style="margin: 0.25rem 0 0; color: #666; font-size: 0.9rem;">Gestión de artículos del blog</p>
</div>
<div style="display: flex; gap: 1rem; align-items: center;">
<a href="../blog.php" target="_blank" class="btn-accion" style="background: #eaf0ee; color: #003f52;">Ver Blog</a>
<a href="editor.php" class="btn-nuevo">+ Nuevo Artículo</a>
<a href="logout.php" class="logout-link">Cerrar sesión</a>
</div>
</div>
<div class="admin-container">
<table class="posts-table">
<thead>
<tr>
<th>Título</th>
<th>Categoría</th>
<th>Autor</th>
<th>Estado</th>
<th>Fecha</th>
<th>Vistas</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php if (empty($posts)): ?>
<tr>
<td colspan="7" style="text-align: center; padding: 3rem; color: #666;">
No hay artículos creados aún. <a href="editor.php" style="color: #ba124d;">Crear el primero</a>
</td>
</tr>
<?php else: ?>
<?php foreach ($posts as $post): ?>
<tr>
<td><?= htmlspecialchars($post['titulo']) ?></td>
<td><?= htmlspecialchars($post['categoria']) ?></td>
<td><?= htmlspecialchars($post['autor_nombre']) ?></td>
<td>
<span class="estado-badge estado-<?= $post['estado'] ?>">
<?= $post['estado'] ?>
</span>
</td>
<td><?= date('d/m/Y', strtotime($post['created_at'])) ?></td>
<td><?= $post['vistas'] ?></td>
<td class="acciones">
<a href="editor.php?id=<?= $post['id'] ?>" class="btn-accion btn-editar">Editar</a>
<a href="eliminar.php?id=<?= $post['id'] ?>" class="btn-accion btn-eliminar" onclick="return confirm('¿Eliminar este artículo?')">Eliminar</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</body>
</html>
+294
View File
@@ -0,0 +1,294 @@
<?php
require_once '../config/db.php';
require_once 'auth.php';
verificarAuth();
$admin = getAdminActual();
$post = null;
$modo_edicion = false;
// Si viene ID, es edición
if (isset($_GET['id'])) {
$stmt = $pdo->prepare("SELECT * FROM blog_posts WHERE id = ?");
$stmt->execute([$_GET['id']]);
$post = $stmt->fetch();
if (!$post) {
die('Artículo no encontrado');
}
$modo_edicion = true;
}
// Procesar formulario
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$titulo = trim($_POST['titulo']);
$slug = strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $titulo)));
$extracto = trim($_POST['extracto']);
$contenido = $_POST['contenido'];
$categoria = $_POST['categoria'];
$tags = trim($_POST['tags']);
$destacado = isset($_POST['destacado']) ? 1 : 0;
$estado = $_POST['estado'];
$tiempo_lectura = (int)$_POST['tiempo_lectura'];
$autor_id = $admin['id'];
// Subida de imagen
$imagen = $post['imagen'] ?? '';
if (isset($_FILES['imagen']) && $_FILES['imagen']['error'] === UPLOAD_ERR_OK) {
$allowed = ['image/jpeg', 'image/png', 'image/webp'];
$max_size = 5 * 1024 * 1024; // 5MB
if (!in_array($_FILES['imagen']['type'], $allowed)) {
$error = 'Solo se permiten imágenes JPG, PNG o WebP';
} elseif ($_FILES['imagen']['size'] > $max_size) {
$error = 'La imagen no puede superar los 5MB';
} else {
$extension = pathinfo($_FILES['imagen']['name'], PATHINFO_EXTENSION);
$nombre_imagen = $slug . '-' . time() . '.' . $extension;
$ruta_destino = '../assets/blog/' . $nombre_imagen;
if (!is_dir('../assets/blog')) {
mkdir('../assets/blog', 0755, true);
}
if (move_uploaded_file($_FILES['imagen']['tmp_name'], $ruta_destino)) {
$imagen = 'assets/blog/' . $nombre_imagen;
} else {
$error = 'Error al subir la imagen';
}
}
}
if (!isset($error)) {
if ($modo_edicion) {
$stmt = $pdo->prepare("
UPDATE blog_posts SET
titulo = ?, slug = ?, extracto = ?, contenido = ?,
categoria = ?, tags = ?, imagen = ?, destacado = ?,
estado = ?, tiempo_lectura = ?
WHERE id = ?
");
$stmt->execute([
$titulo, $slug, $extracto, $contenido,
$categoria, $tags, $imagen, $destacado,
$estado, $tiempo_lectura, $post['id']
]);
} else {
$stmt = $pdo->prepare("
INSERT INTO blog_posts
(titulo, slug, extracto, contenido, categoria, tags, imagen, autor_id, destacado, estado, tiempo_lectura)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$titulo, $slug, $extracto, $contenido,
$categoria, $tags, $imagen, $autor_id, $destacado,
$estado, $tiempo_lectura
]);
}
header('Location: dashboard.php');
exit;
}
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $modo_edicion ? 'Editar' : 'Nuevo' ?> Artículo - Amor y Roma</title>
<script src="https://cdn.tiny.mce.com/1/tinymce/6/tinymce.min.js" referrerpolicy="origin"></script>
<link rel="stylesheet" href="../style/main.css">
<style>
.admin-header {
background: white;
padding: 1.5rem 2rem;
border-bottom: 1px solid rgba(0, 63, 82, 0.12);
display: flex;
justify-content: space-between;
align-items: center;
}
.editor-container {
max-width: 1000px;
margin: 0 auto;
padding: 2rem;
}
.form-group {
margin-bottom: 1.5rem;
}
label {
display: block;
margin-bottom: 0.5rem;
color: #003f52;
font-weight: 600;
}
input[type="text"],
input[type="number"],
select,
textarea {
width: 100%;
padding: 0.75rem;
border: 1.5px solid rgba(0, 63, 82, 0.2);
border-radius: 8px;
font-size: 1rem;
box-sizing: border-box;
font-family: 'Raleway', sans-serif;
}
textarea {
resize: vertical;
min-height: 100px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.checkbox-group {
display: flex;
align-items: center;
gap: 0.5rem;
}
.checkbox-group input[type="checkbox"] {
width: 20px;
height: 20px;
cursor: pointer;
}
.btn-guardar {
background: #ba124d;
color: white;
padding: 0.85rem 2rem;
border: none;
border-radius: 24px;
font-weight: 600;
cursor: pointer;
font-size: 1rem;
}
.btn-guardar:hover {
background: #9c0f40;
}
.btn-cancelar {
background: #eaf0ee;
color: #003f52;
padding: 0.85rem 2rem;
border: none;
border-radius: 24px;
font-weight: 600;
cursor: pointer;
font-size: 1rem;
text-decoration: none;
display: inline-block;
}
.imagen-preview {
margin-top: 1rem;
max-width: 300px;
}
.imagen-preview img {
width: 100%;
border-radius: 8px;
}
.error {
background: #fee;
color: #c00;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
}
</style>
<script>
tinymce.init({
selector: '#contenido',
height: 500,
plugins: 'anchor autolink charmap codesample emoticons image link lists media searchreplace table visualblocks wordcount',
toolbar: 'undo redo | blocks fontfamily fontsize | bold italic underline strikethrough | link image media table | align lineheight | numlist bullist indent outdent | emoticons charmap | removeformat',
content_style: 'body { font-family: Raleway, sans-serif; font-size: 16px; }',
language: 'es'
});
</script>
</head>
<body>
<div class="admin-header">
<h2 style="margin: 0; font-family: 'Literata', serif;"><?= $modo_edicion ? 'Editar' : 'Nuevo' ?> Artículo</h2>
<a href="dashboard.php" class="btn-cancelar">← Volver al dashboard</a>
</div>
<div class="editor-container">
<?php if (isset($error)): ?>
<div class="error"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<form method="POST" enctype="multipart/form-data">
<div class="form-group">
<label for="titulo">Título del artículo *</label>
<input type="text" id="titulo" name="titulo" required value="<?= htmlspecialchars($post['titulo'] ?? '') ?>">
</div>
<div class="form-group">
<label for="extracto">Extracto (resumen corto) *</label>
<textarea id="extracto" name="extracto" required><?= htmlspecialchars($post['extracto'] ?? '') ?></textarea>
</div>
<div class="form-group">
<label for="contenido">Contenido completo *</label>
<textarea id="contenido" name="contenido" required><?= htmlspecialchars($post['contenido'] ?? '') ?></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label for="categoria">Categoría *</label>
<select id="categoria" name="categoria" required>
<option value="">Seleccionar...</option>
<option value="branding" <?= ($post['categoria'] ?? '') === 'branding' ? 'selected' : '' ?>>Branding</option>
<option value="web" <?= ($post['categoria'] ?? '') === 'web' ? 'selected' : '' ?>>Web</option>
<option value="redes" <?= ($post['categoria'] ?? '') === 'redes' ? 'selected' : '' ?>>Redes</option>
<option value="automatizaciones" <?= ($post['categoria'] ?? '') === 'automatizaciones' ? 'selected' : '' ?>>Automatizaciones</option>
<option value="paid" <?= ($post['categoria'] ?? '') === 'paid' ? 'selected' : '' ?>>Paid Media</option>
<option value="estrategia" <?= ($post['categoria'] ?? '') === 'estrategia' ? 'selected' : '' ?>>Estrategia</option>
<option value="casos" <?= ($post['categoria'] ?? '') === 'casos' ? 'selected' : '' ?>>Casos</option>
</select>
</div>
<div class="form-group">
<label for="estado">Estado *</label>
<select id="estado" name="estado" required>
<option value="borrador" <?= ($post['estado'] ?? 'borrador') === 'borrador' ? 'selected' : '' ?>>Borrador</option>
<option value="publicado" <?= ($post['estado'] ?? '') === 'publicado' ? 'selected' : '' ?>>Publicado</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="tags">Tags (separados por coma)</label>
<input type="text" id="tags" name="tags" value="<?= htmlspecialchars($post['tags'] ?? '') ?>" placeholder="ej: branding, identidad, logo">
</div>
<div class="form-group">
<label for="tiempo_lectura">Tiempo de lectura (minutos)</label>
<input type="number" id="tiempo_lectura" name="tiempo_lectura" min="1" value="<?= $post['tiempo_lectura'] ?? 5 ?>">
</div>
</div>
<div class="form-group">
<label for="imagen">Imagen destacada *</label>
<input type="file" id="imagen" name="imagen" accept="image/*" <?= !$modo_edicion ? 'required' : '' ?>>
<?php if ($post && $post['imagen']): ?>
<div class="imagen-preview">
<img src="../<?= htmlspecialchars($post['imagen']) ?>" alt="Imagen actual">
<p style="font-size: 0.85rem; color: #666; margin-top: 0.5rem;">Imagen actual: <?= basename($post['imagen']) ?></p>
</div>
<?php endif; ?>
</div>
<div class="form-group">
<div class="checkbox-group">
<input type="checkbox" id="destacado" name="destacado" value="1" <?= ($post['destacado'] ?? 0) ? 'checked' : '' ?>>
<label for="destacado" style="margin: 0;">Marcar como destacado (aparece en la sección "Más leídos")</label>
</div>
</div>
<div style="display: flex; gap: 1rem; margin-top: 2rem;">
<button type="submit" class="btn-guardar"> Guardar Artículo</button>
<a href="dashboard.php" class="btn-cancelar">Cancelar</a>
</div>
</form>
</div>
</body>
</html>
+29
View File
@@ -0,0 +1,29 @@
<?php
require_once '../config/db.php';
require_once 'auth.php';
verificarAuth();
if (!isset($_GET['id'])) {
header('Location: dashboard.php');
exit;
}
// Obtener el artículo para borrar la imagen
$stmt = $pdo->prepare("SELECT imagen FROM blog_posts WHERE id = ?");
$stmt->execute([$_GET['id']]);
$post = $stmt->fetch();
if ($post && $post['imagen']) {
$ruta_imagen = '../' . $post['imagen'];
if (file_exists($ruta_imagen)) {
unlink($ruta_imagen);
}
}
// Eliminar el artículo
$stmt = $pdo->prepare("DELETE FROM blog_posts WHERE id = ?");
$stmt->execute([$_GET['id']]);
header('Location: dashboard.php');
exit;
?>
+130
View File
@@ -0,0 +1,130 @@
<?php
require_once '../config/db.php';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = trim($_POST['email']);
$password = $_POST['password'];
$stmt = $pdo->prepare("SELECT id, nombre, password FROM admin_users WHERE email = ?");
$stmt->execute([$email]);
$user = $stmt->fetch();
// Comparación directa sin hash (como pediste)
if ($user && $password === $user['password']) {
$_SESSION['admin_id'] = $user['id'];
$_SESSION['admin_nombre'] = $user['nombre'];
header('Location: dashboard.php');
exit;
} else {
$error = 'Email o contraseña incorrectos';
}
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Login - Amor y Roma</title>
<style>
body {
font-family: 'Raleway', sans-serif;
background: #eaf0ee;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
margin: 0;
}
.login-container {
background: white;
padding: 3rem;
border-radius: 16px;
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
width: 100%;
max-width: 400px;
}
h1 {
color: #221728;
margin-bottom: 0.5rem;
font-family: 'Literata', serif;
font-style: italic;
}
.subtitle {
color: #666;
margin-bottom: 2rem;
font-size: 0.9rem;
}
.form-group {
margin-bottom: 1.5rem;
}
label {
display: block;
margin-bottom: 0.5rem;
color: #003f52;
font-weight: 600;
font-size: 0.9rem;
}
input[type="email"],
input[type="password"] {
width: 100%;
padding: 0.75rem;
border: 1.5px solid rgba(0, 63, 82, 0.2);
border-radius: 8px;
font-size: 1rem;
box-sizing: border-box;
}
input:focus {
outline: none;
border-color: #ba124d;
}
.btn-login {
width: 100%;
padding: 0.85rem;
background: #ba124d;
color: white;
border: none;
border-radius: 24px;
font-weight: 600;
cursor: pointer;
font-size: 1rem;
transition: background 0.3s;
}
.btn-login:hover {
background: #9c0f40;
}
.error {
background: #fee;
color: #c00;
padding: 0.75rem;
border-radius: 8px;
margin-bottom: 1rem;
font-size: 0.9rem;
}
</style>
</head>
<body>
<div class="login-container">
<h1>Amor y Roma</h1>
<p class="subtitle">Panel de Administración del Blog</p>
<?php if ($error): ?>
<div class="error"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<form method="POST">
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" required autofocus>
</div>
<div class="form-group">
<label for="password">Contraseña</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit" class="btn-login">Ingresar</button>
</form>
</div>
</body>
</html>
+6
View File
@@ -0,0 +1,6 @@
<?php
session_start();
session_destroy();
header('Location: login.php');
exit;
?>