blog+contacto php listo
This commit is contained in:
@@ -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();
|
||||
}
|
||||
?>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
@@ -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>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
session_start();
|
||||
session_destroy();
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
?>
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
require_once 'config/db.php';
|
||||
|
||||
if (!isset($_GET['id'])) {
|
||||
header('Location: blog.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Obtener artículo
|
||||
$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
|
||||
WHERE p.id = ? AND p.estado = 'publicado'
|
||||
");
|
||||
$stmt->execute([$_GET['id']]);
|
||||
$post = $stmt->fetch();
|
||||
|
||||
if (!$post) {
|
||||
header('Location: blog.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Incrementar vistas
|
||||
$stmt = $pdo->prepare("UPDATE blog_posts SET vistas = vistas + 1 WHERE id = ?");
|
||||
$stmt->execute([$post['id']]);
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="light">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="<?= htmlspecialchars($post['extracto']) ?>">
|
||||
<title><?= htmlspecialchars($post['titulo']) ?> | Blog Amor y Roma</title>
|
||||
<link rel="stylesheet" href="style/main.css">
|
||||
<style>
|
||||
.articulo-header {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 4rem 2rem 2rem;
|
||||
}
|
||||
.articulo-imagen {
|
||||
width: 100%;
|
||||
max-height: 500px;
|
||||
object-fit: cover;
|
||||
border-radius: 16px;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
.articulo-contenido {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 0 2rem 4rem;
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.articulo-contenido h2,
|
||||
.articulo-contenido h3 {
|
||||
margin-top: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.articulo-contenido p {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.articulo-contenido img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
.articulo-meta {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
margin: 1rem 0 2rem;
|
||||
padding: 1rem 0;
|
||||
border-top: 1px solid rgba(0, 63, 82, 0.12);
|
||||
border-bottom: 1px solid rgba(0, 63, 82, 0.12);
|
||||
}
|
||||
.volver-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: #ba124d;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="menu-container"></div>
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<div class="articulo-header">
|
||||
<a href="blog.php" class="volver-link">← Volver al blog</a>
|
||||
<span class="badge-filosofia"><?= ucfirst(htmlspecialchars($post['categoria'])) ?></span>
|
||||
<h1 style="font-size: clamp(2rem, 4vw, 3rem); margin: 1rem 0;"><?= htmlspecialchars($post['titulo']) ?></h1>
|
||||
<div class="articulo-meta">
|
||||
<span>📅 <?= date('d de F Y', strtotime($post['created_at'])) ?></span>
|
||||
<span> <?= $post['tiempo_lectura'] ?> min de lectura</span>
|
||||
<span>✍️ <?= htmlspecialchars($post['autor_nombre']) ?></span>
|
||||
</div>
|
||||
<img src="<?= htmlspecialchars($post['imagen']) ?>" alt="<?= htmlspecialchars($post['titulo']) ?>" class="articulo-imagen">
|
||||
</div>
|
||||
|
||||
<div class="articulo-contenido">
|
||||
<?= $post['contenido'] ?>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<div id="footer-container"></div>
|
||||
<script src="scripts/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -4,6 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Blog de Amor y Roma. Estrategia, branding, desarrollo web y marketing digital con esencia y solidez.">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.0/css/all.min.css">
|
||||
<title>Blog | Amor y Roma — Recursos y Estrategias</title>
|
||||
<link rel="stylesheet" href="style/main.css">
|
||||
</head>
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
require_once 'config/db.php';
|
||||
|
||||
// Obtener artículos publicados
|
||||
$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
|
||||
WHERE p.estado = 'publicado'
|
||||
ORDER BY p.created_at DESC
|
||||
");
|
||||
$stmt->execute();
|
||||
$posts = $stmt->fetchAll();
|
||||
|
||||
// Obtener destacados
|
||||
$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
|
||||
WHERE p.estado = 'publicado' AND p.destacado = 1
|
||||
ORDER BY p.vistas DESC
|
||||
LIMIT 3
|
||||
");
|
||||
$stmt->execute();
|
||||
$destacados = $stmt->fetchAll();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="light">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Blog de Amor y Roma. Estrategia, branding, desarrollo web y marketing digital con esencia y solidez.">
|
||||
<title>Blog | Amor y Roma — Recursos y Estrategias</title>
|
||||
<link rel="stylesheet" href="style/main.css">
|
||||
<link rel="stylesheet" href="style/blog.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="menu-container"></div>
|
||||
|
||||
<main>
|
||||
<!-- HERO -->
|
||||
<section class="blog-hero-compact">
|
||||
<div class="hero-compact-content">
|
||||
<span class="badge-filosofia">Ideas que construyen marcas</span>
|
||||
<h1 class="hero-compact-title">Estrategia, diseño y tecnología explicados sin tecnicismos</h1>
|
||||
<p class="hero-compact-subtitle">
|
||||
Consejos prácticos de branding, web y marketing digital para hacer crecer tu negocio con esencia y estructura.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- SECCIÓN 2: HEADER DE FILTROS -->
|
||||
<section class="blog-filters-section" id="blog-filters-header">
|
||||
<div class="filters-container">
|
||||
|
||||
<!-- Fila superior: Buscador + Filtros principales -->
|
||||
<div class="filters-top-row">
|
||||
<div class="search-wrapper-compact">
|
||||
<span class="search-icon">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="11" cy="11" r="8"/>
|
||||
<path d="m21 21-4.35-4.35"/>
|
||||
</svg>
|
||||
</span>
|
||||
<input type="text" id="blog-search" placeholder="Buscá por tema: branding, web, redes..." autocomplete="off" aria-label="Buscar artículos">
|
||||
<button class="search-clear" id="search-clear" type="button" aria-label="Limpiar búsqueda">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M18 6 6 18M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="filter-group-compact">
|
||||
<button class="filter-btn active" data-filter="all">Todos</button>
|
||||
<button class="filter-btn" data-filter="destacados">Destacados</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fila inferior: Categorías + Ordenamiento -->
|
||||
<div class="filters-bottom-row">
|
||||
<div class="filter-categories-compact">
|
||||
<button class="category-btn active" data-category="all">Todas</button>
|
||||
<button class="category-btn" data-category="branding">Branding</button>
|
||||
<button class="category-btn" data-category="web">Web</button>
|
||||
<button class="category-btn" data-category="redes">Redes</button>
|
||||
<button class="category-btn" data-category="automatizaciones">Automatizaciones</button>
|
||||
<button class="category-btn" data-category="paid">Paid Media</button>
|
||||
<button class="category-btn" data-category="estrategia">Estrategia</button>
|
||||
<button class="category-btn" data-category="casos">Casos</button>
|
||||
</div>
|
||||
|
||||
<div class="filter-secondary-compact">
|
||||
<select id="sort-select" class="sort-select-compact">
|
||||
<option value="recent">Más recientes</option>
|
||||
<option value="popular">Más populares</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info de resultados -->
|
||||
<div class="results-info-compact">
|
||||
<span id="results-count">Mostrando <strong>todos</strong> los artículos</span>
|
||||
<button class="reset-filters-compact" id="reset-filters">Limpiar filtros</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- DESTACADOS -->
|
||||
<?php if (!empty($destacados)): ?>
|
||||
<section class="featured-posts" id="featured-posts">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">Más leídos</h2>
|
||||
<p class="section-subtitle">Los artículos que más están ayudando a marcas como la tuya.</p>
|
||||
</div>
|
||||
|
||||
<div class="featured-grid">
|
||||
<?php foreach ($destacados as $post): ?>
|
||||
<article class="featured-card" data-categories="<?= htmlspecialchars($post['categoria']) ?>">
|
||||
<div class="featured-image">
|
||||
<img src="<?= htmlspecialchars($post['imagen']) ?>" alt="<?= htmlspecialchars($post['titulo']) ?>">
|
||||
<span class="featured-badge">Destacado</span>
|
||||
</div>
|
||||
<div class="featured-content">
|
||||
<div class="featured-meta">
|
||||
<span class="category-tag <?= htmlspecialchars($post['categoria']) ?>"><?= ucfirst(htmlspecialchars($post['categoria'])) ?></span>
|
||||
<time datetime="<?= $post['created_at'] ?>"><?= date('d M Y', strtotime($post['created_at'])) ?></time>
|
||||
</div>
|
||||
<h3 class="featured-title"><?= htmlspecialchars($post['titulo']) ?></h3>
|
||||
<p class="featured-excerpt"><?= htmlspecialchars($post['extracto']) ?></p>
|
||||
<div class="featured-footer">
|
||||
<span class="reading-time">⏱ <?= $post['tiempo_lectura'] ?> min de lectura</span>
|
||||
<a href="articulo.php?id=<?= $post['id'] ?>" class="btn-read">Leer artículo →</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- GRID DE ARTÍCULOS -->
|
||||
<section class="blog-grid-section">
|
||||
<div class="blog-grid" id="blog-grid">
|
||||
<?php foreach ($posts as $post): ?>
|
||||
<article class="blog-card" data-categories="<?= htmlspecialchars($post['categoria']) ?>">
|
||||
<div class="card-image">
|
||||
<img src="<?= htmlspecialchars($post['imagen']) ?>" alt="<?= htmlspecialchars($post['titulo']) ?>">
|
||||
<span class="card-category <?= htmlspecialchars($post['categoria']) ?>"><?= ucfirst(htmlspecialchars($post['categoria'])) ?></span>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="card-meta">
|
||||
<time datetime="<?= $post['created_at'] ?>"><?= date('d M Y', strtotime($post['created_at'])) ?></time>
|
||||
<span class="reading-time-mini"><?= $post['tiempo_lectura'] ?> min</span>
|
||||
</div>
|
||||
<h3 class="card-title"><?= htmlspecialchars($post['titulo']) ?></h3>
|
||||
<p class="card-excerpt"><?= htmlspecialchars($post['extracto']) ?></p>
|
||||
<a href="articulo.php?id=<?= $post['id'] ?>" class="card-link">Leer artículo</a>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<?php if (empty($posts)): ?>
|
||||
<div class="empty-state" id="empty-state">
|
||||
<div class="empty-icon">📝</div>
|
||||
<h3>Aún no hay artículos publicados</h3>
|
||||
<p>Volvé pronto para encontrar contenido de valor sobre branding, web y marketing digital.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<!-- NEWSLETTER -->
|
||||
<section class="newsletter-section">
|
||||
<div class="newsletter-card">
|
||||
<div class="newsletter-content">
|
||||
<h2 class="newsletter-title">Ideas frescas en tu inbox</h2>
|
||||
<p class="newsletter-subtitle">
|
||||
Suscribite y recibí nuestras mejores estrategias de branding, web y marketing digital.
|
||||
</p>
|
||||
</div>
|
||||
<form class="newsletter-form" id="newsletter-form">
|
||||
<input type="email" placeholder="Tu email profesional" required style="padding: 0.85rem; border: 1.5px solid rgba(0, 63, 82, 0.2); border-radius: 24px; min-width: 300px;">
|
||||
<button type="submit" class="btn btn-primary">Suscribirme</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div id="footer-container"></div>
|
||||
|
||||
<script src="scripts/main.js"></script>
|
||||
<script src="scripts/blog.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* Configuración general del formulario de contacto.
|
||||
* NO subir este archivo a repos públicos.
|
||||
*/
|
||||
|
||||
// ─── SMTP (DonWeb / Ferozo) ────────────────────────────────────────────
|
||||
// Reemplazá los valores por los reales de tu casilla contacto@amoryroma.com.ar
|
||||
define('SMTP_HOST', 'dtc036.ferozo.com'); // o el que te dio DonWeb
|
||||
define('SMTP_PORT', 465); // 587 (TLS) o 465 (SSL)
|
||||
define('SMTP_SECURE', 'ssl'); // 'tls' o 'ssl'
|
||||
define('SMTP_USER', 'contacto@amoryroma.com.ar');
|
||||
define('SMTP_PASS', '1J@oUz7f');
|
||||
define('SMTP_FROM', 'contacto@amoryroma.com.ar');
|
||||
define('SMTP_FROM_NAME', 'Amor & Roma');
|
||||
|
||||
// ─── Destinatarios ─────────────────────────────────────────────────────
|
||||
define('MAIL_TO', 'contacto@amoryroma.com.ar');
|
||||
define('MAIL_TO_NAME', 'Amor & Roma');
|
||||
|
||||
// ─── Redirección post-éxito ────────────────────────────────────────────
|
||||
define('REDIRECT_URL', 'inicio.html');
|
||||
define('REDIRECT_MS', 4000); // milisegundos antes de redirigir
|
||||
|
||||
// ─── Modo debug ────────────────────────────────────────────────────────
|
||||
// true → muestra errores SMTP en la respuesta JSON (solo para probar)
|
||||
// false → producción (recomendado)
|
||||
define('DEBUG_MODE', false);
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
// Configuración de base de datos
|
||||
define('DB_HOST', 'localhost');
|
||||
define('DB_NAME', 'w360284_blog');
|
||||
define('DB_USER', 'w360284_blog');
|
||||
define('DB_PASS', 'f@IuVS8sjLLm7aN');
|
||||
define('DB_CHARSET', 'utf8mb4');
|
||||
|
||||
// Crear conexión PDO
|
||||
try {
|
||||
$pdo = new PDO(
|
||||
"mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET,
|
||||
DB_USER,
|
||||
DB_PASS,
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false
|
||||
]
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
die("Error de conexión: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// Iniciar sesión si no está iniciada
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
?>
|
||||
+17
-3
@@ -4,8 +4,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Contactá a Amor y Roma. Contanos sobre tu proyecto y coordinemos un diagnóstico estratégico sin compromiso.">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.0/css/all.min.css">
|
||||
<title>Contacto | Amor y Roma — Agencia Boutique</title>
|
||||
<link rel="stylesheet" href="style/main.css">
|
||||
<link rel="stylesheet" href="style/contacto.css">
|
||||
<link rel="stylesheet" href="style/modal.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="menu-container"></div>
|
||||
@@ -174,6 +177,11 @@
|
||||
Acepto que mis datos sean utilizados para contactarme respecto a mi consulta. <span class="form-required">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<!-- Honeypot anti-spam: invisible para humanos -->
|
||||
<div class="form-honeypot" aria-hidden="true">
|
||||
<label for="website">No completar este campo</label>
|
||||
<input type="text" id="website" name="website" tabindex="-1" autocomplete="off">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-form-enviar">
|
||||
Enviar consulta
|
||||
@@ -243,14 +251,20 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- MODAL DE ÉXITO -->
|
||||
<div id="modal-contacto" role="dialog" aria-modal="true" aria-hidden="true" aria-labelledby="modal-titulo">
|
||||
<div class="modal-card">
|
||||
<div class="modal-icono" aria-hidden="true">✓</div>
|
||||
<h2 id="modal-titulo" class="modal-titulo">¡Mensaje enviado!</h2>
|
||||
<p class="modal-mensaje">Gracias por escribirnos. Te contactaremos en menos de 48 horas hábiles.</p>
|
||||
<button type="button" class="modal-cerrar">Volver al inicio</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div id="footer-container"></div>
|
||||
|
||||
<script src="scripts/main.js"></script>
|
||||
<script src="scripts/efecto.js"></script>
|
||||
<script src="scripts/contacto.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="style/contacto.css">
|
||||
</body>
|
||||
</html>
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
/**
|
||||
* Endpoint AJAX del formulario de contacto.
|
||||
* Recibe POST y responde JSON.
|
||||
*/
|
||||
|
||||
// ── Silenciar salida directa, solo JSON ────────────────────────────────
|
||||
if (ob_get_length() > 0) { ob_clean(); }
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
|
||||
// ── Errores a log, no a pantalla ───────────────────────────────────────
|
||||
ini_set('display_errors', 0);
|
||||
ini_set('display_startup_errors', 0);
|
||||
ini_set('log_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
require_once __DIR__ . '/config.php';
|
||||
require_once __DIR__ . '/validar.php';
|
||||
|
||||
// ── Respuesta helper ───────────────────────────────────────────────────
|
||||
function responder($status, $message, $code = 200, $extra = []) {
|
||||
http_response_code($code);
|
||||
echo json_encode(array_merge([
|
||||
'status' => $status,
|
||||
'message' => $message,
|
||||
], $extra));
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Método ─────────────────────────────────────────────────────────────
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
||||
responder('error', 'Método no permitido.', 405);
|
||||
}
|
||||
|
||||
// ── Honeypot ───────────────────────────────────────────────────────────
|
||||
if (!empty($_POST['website'])) {
|
||||
// Fingimos éxito para no darle pistas al bot
|
||||
responder('success', '¡Gracias! Te contactaremos a la brevedad.');
|
||||
}
|
||||
|
||||
// ── Validación ─────────────────────────────────────────────────────────
|
||||
$resultado = validar_contacto($_POST);
|
||||
if (!$resultado['ok']) {
|
||||
responder('error', implode(' ', $resultado['errores']), 400);
|
||||
}
|
||||
$d = $resultado['datos'];
|
||||
|
||||
// ── PHPMailer ──────────────────────────────────────────────────────────
|
||||
require_once __DIR__ . '/lib/PHPMailer.php';
|
||||
require_once __DIR__ . '/lib/SMTP.php';
|
||||
require_once __DIR__ . '/lib/Exception.php';
|
||||
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\Exception;
|
||||
use PHPMailer\PHPMailer\SMTP;
|
||||
|
||||
// ── Mapas para mostrar etiquetas legibles ─────────────────────────────
|
||||
$etiquetas = [
|
||||
'servicio' => [
|
||||
'branding' => 'Branding & Identidad',
|
||||
'web' => 'Desarrollo Web a Medida',
|
||||
'rrss' => 'Gestión de Redes Sociales',
|
||||
'automatizaciones'=> 'Automatizaciones & Flujos',
|
||||
'paid' => 'Paid Media & Campañas',
|
||||
'ecosistema' => 'Ecosistema Digital Completo',
|
||||
'otro' => 'Otro / Aún no definido',
|
||||
],
|
||||
'etapa' => [
|
||||
'idea' => 'Solo tengo la idea',
|
||||
'inicio' => 'Estoy empezando',
|
||||
'marcha' => 'En marcha, necesito orden',
|
||||
'redisenio' => 'Rediseñar / reposicionar',
|
||||
'escalar' => 'Escalar lo existente',
|
||||
],
|
||||
'presupuesto' => [
|
||||
'esencial' => 'Hasta $250.000 (Esencial)',
|
||||
'impulso' => '$250.000 – $500.000 (Impulso)',
|
||||
'premium' => '$500.000 – $800.000 (Premium)',
|
||||
'elite' => 'Más de $800.000 (Élite)',
|
||||
'proyecto' => 'Proyecto cerrado (one-off)',
|
||||
'no-se' => 'A evaluar por el equipo',
|
||||
],
|
||||
'origen' => [
|
||||
'instagram' => 'Instagram',
|
||||
'linkedin' => 'LinkedIn',
|
||||
'referido' => 'Referido',
|
||||
'google' => 'Google',
|
||||
'otro' => 'Otro',
|
||||
],
|
||||
];
|
||||
|
||||
$mail = new PHPMailer(true);
|
||||
|
||||
try {
|
||||
// SMTP
|
||||
$mail->isSMTP();
|
||||
$mail->Host = SMTP_HOST;
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Username = SMTP_USER;
|
||||
$mail->Password = SMTP_PASS;
|
||||
$mail->SMTPSecure = SMTP_SECURE;
|
||||
$mail->Port = SMTP_PORT;
|
||||
$mail->CharSet = 'UTF-8';
|
||||
$mail->SMTPDebug = 0;
|
||||
|
||||
// De / Para
|
||||
$mail->setFrom(SMTP_FROM, SMTP_FROM_NAME);
|
||||
$mail->addAddress(MAIL_TO, MAIL_TO_NAME);
|
||||
$mail->addReplyTo($d['email'], $d['nombre']);
|
||||
|
||||
// Contenido
|
||||
$mail->Subject = 'Nuevo contacto: ' . $d['nombre'] . ($d['empresa'] ? ' (' . $d['empresa'] . ')' : '');
|
||||
$mail->isHTML(true);
|
||||
|
||||
$servicio_txt = $etiquetas['servicio'][$d['servicio']] ?? '—';
|
||||
$etapa_txt = $etiquetas['etapa'][$d['etapa']] ?? '—';
|
||||
$presupuesto_txt = $etiquetas['presupuesto'][$d['presupuesto']] ?? '—';
|
||||
$origen_txt = $etiquetas['origen'][$d['origen']] ?? '—';
|
||||
|
||||
$rows = [
|
||||
'Nombre' => $d['nombre'],
|
||||
'Empresa / Marca' => $d['empresa'] ?: '—',
|
||||
'Email' => $d['email'],
|
||||
'Teléfono' => $d['telefono'] ?: '—',
|
||||
'Servicio' => $servicio_txt,
|
||||
'Etapa' => $etapa_txt,
|
||||
'Presupuesto' => $presupuesto_txt,
|
||||
'Cómo nos conoció'=> $origen_txt,
|
||||
];
|
||||
|
||||
$html = '<div style="font-family:Arial,sans-serif;font-size:14px;color:#222;max-width:640px">';
|
||||
$html .= '<h2 style="margin:0 0 16px;color:#111">Nueva consulta desde el sitio</h2>';
|
||||
$html .= '<table cellpadding="8" cellspacing="0" style="border-collapse:collapse;width:100%">';
|
||||
foreach ($rows as $label => $valor) {
|
||||
$html .= '<tr>';
|
||||
$html .= '<td style="background:#f5f2ec;border:1px solid #e5e0d5;font-weight:bold;width:180px">' . htmlspecialchars($label) . '</td>';
|
||||
$html .= '<td style="border:1px solid #e5e0d5">' . htmlspecialchars($valor) . '</td>';
|
||||
$html .= '</tr>';
|
||||
}
|
||||
$html .= '</table>';
|
||||
$html .= '<h3 style="margin:24px 0 8px;color:#111">Mensaje</h3>';
|
||||
$html .= '<div style="white-space:pre-wrap;background:#fafafa;border:1px solid #eee;padding:12px;border-radius:6px">'
|
||||
. htmlspecialchars($d['mensaje']) . '</div>';
|
||||
$html .= '</div>';
|
||||
|
||||
$mail->Body = $html;
|
||||
$mail->AltBody = "Nueva consulta\n\n"
|
||||
. "Nombre: {$d['nombre']}\n"
|
||||
. "Empresa: {$d['empresa']}\n"
|
||||
. "Email: {$d['email']}\n"
|
||||
. "Teléfono: {$d['telefono']}\n"
|
||||
. "Servicio: $servicio_txt\n"
|
||||
. "Etapa: $etapa_txt\n"
|
||||
. "Presupuesto: $presupuesto_txt\n"
|
||||
. "Origen: $origen_txt\n\n"
|
||||
. "Mensaje:\n{$d['mensaje']}\n";
|
||||
|
||||
$mail->send();
|
||||
|
||||
responder('success', '¡Gracias! Te contactaremos en menos de 48 horas hábiles.', 200, [
|
||||
'redirect' => REDIRECT_URL,
|
||||
'delay' => REDIRECT_MS,
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('[contacto] Falló el envío: ' . $mail->ErrorInfo);
|
||||
$msg = DEBUG_MODE
|
||||
? 'Error SMTP: ' . $mail->ErrorInfo
|
||||
: 'No pudimos enviar tu mensaje. Probá por WhatsApp o intentá más tarde.';
|
||||
responder('error', $msg, 500);
|
||||
}
|
||||
+4
-2
@@ -15,8 +15,9 @@
|
||||
<!-- Sección Hero -->
|
||||
<section id="hero" class="hero-section">
|
||||
<div class="hero-content">
|
||||
<h1 class="hero-title">Marcas con esencia, construidas con estrategia</h1>
|
||||
<p class="hero-subtitle">Diseñamos servicios que se adaptan a tu realidad, para ayudarte a crecer sin perder lo que te hace único</p>
|
||||
<span class="badge-filosofia">Agencia Boutique de Marketing Digital</span>
|
||||
<h1 class="hero-title">En un mundo de ruido, solo lo auténtico resuena</h1>
|
||||
<p class="hero-subtitle">Combinamos la pasión creativa con la ingeniería de procesos para desarrollar tu identidad, sitio web y automatizaciones. <br>Porque lo auténtico, cuando está bien construido, siempre deja eco.</p>
|
||||
<div class="hero-cta-group">
|
||||
<a href="#" class="btn btn-primary hero-cta-primary">Hablemos</a>
|
||||
<a href="inicio.html#servicios" class="btn btn-secondary hero-cta-secondary">Conocé más</a>
|
||||
@@ -531,6 +532,7 @@
|
||||
<div id="footer-container"></div>
|
||||
|
||||
<script src="scripts/main.js"></script>
|
||||
<script src="scripts/fluid.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHPMailer Exception class.
|
||||
* PHP Version 5.5.
|
||||
*
|
||||
* @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
|
||||
*
|
||||
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
|
||||
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
|
||||
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
|
||||
* @author Brent R. Matzelle (original founder)
|
||||
* @copyright 2012 - 2020 Marcus Bointon
|
||||
* @copyright 2010 - 2012 Jim Jagielski
|
||||
* @copyright 2004 - 2009 Andy Prevost
|
||||
* @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
|
||||
* @note This program is distributed in the hope that it will be useful - WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
namespace PHPMailer\PHPMailer;
|
||||
|
||||
/**
|
||||
* PHPMailer exception handler.
|
||||
*
|
||||
* @author Marcus Bointon <phpmailer@synchromedia.co.uk>
|
||||
*/
|
||||
class Exception extends \Exception
|
||||
{
|
||||
/**
|
||||
* Prettify error message output.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function errorMessage()
|
||||
{
|
||||
return '<strong>' . htmlspecialchars($this->getMessage(), ENT_COMPAT | ENT_HTML401) . "</strong><br />\n";
|
||||
}
|
||||
}
|
||||
+5586
File diff suppressed because it is too large
Load Diff
+1617
File diff suppressed because it is too large
Load Diff
+81
-156
@@ -1,185 +1,110 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('Blog JS cargado');
|
||||
|
||||
const searchInput = document.getElementById('blog-search');
|
||||
const searchClear = document.getElementById('search-clear');
|
||||
const blogGrid = document.getElementById('blog-grid');
|
||||
const emptyState = document.getElementById('empty-state');
|
||||
const categoryBtns = document.querySelectorAll('.category-btn');
|
||||
const filterBtns = document.querySelectorAll('.filter-btn');
|
||||
const resetBtn = document.getElementById('reset-filters');
|
||||
const resultsCount = document.getElementById('results-count');
|
||||
const filtersHeader = document.getElementById('blog-filters-header');
|
||||
const emptyState = document.getElementById('empty-state');
|
||||
|
||||
if (!searchInput || !blogGrid) {
|
||||
console.error('Faltan elementos del DOM');
|
||||
return;
|
||||
}
|
||||
const allCards = document.querySelectorAll('.blog-card, .featured-card');
|
||||
let activeCategory = 'all';
|
||||
let activeFilter = 'all';
|
||||
let searchQuery = '';
|
||||
|
||||
let currentFilters = {
|
||||
search: '',
|
||||
category: 'all',
|
||||
featured: 'all',
|
||||
sort: 'recent'
|
||||
};
|
||||
|
||||
// ===== EFECTO SCROLL EN FILTROS =====
|
||||
window.addEventListener('scroll', () => {
|
||||
if (window.scrollY > 100) {
|
||||
filtersHeader.classList.add('is-scrolled');
|
||||
} else {
|
||||
filtersHeader.classList.remove('is-scrolled');
|
||||
}
|
||||
});
|
||||
|
||||
// ===== BUSCADOR =====
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
const value = e.target.value.trim();
|
||||
currentFilters.search = value;
|
||||
|
||||
if (searchClear) {
|
||||
searchClear.classList.toggle('visible', value.length > 0);
|
||||
}
|
||||
|
||||
filterArticles();
|
||||
});
|
||||
|
||||
if (searchClear) {
|
||||
searchClear.addEventListener('click', () => {
|
||||
searchInput.value = '';
|
||||
searchClear.classList.remove('visible');
|
||||
currentFilters.search = '';
|
||||
filterArticles();
|
||||
searchInput.focus();
|
||||
});
|
||||
}
|
||||
|
||||
// ===== FILTROS PRINCIPALES =====
|
||||
document.querySelectorAll('.filter-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
currentFilters.featured = btn.dataset.filter;
|
||||
filterArticles();
|
||||
});
|
||||
});
|
||||
|
||||
// ===== CATEGORÍAS =====
|
||||
document.querySelectorAll('.category-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
document.querySelectorAll('.category-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
currentFilters.category = btn.dataset.category;
|
||||
filterArticles();
|
||||
});
|
||||
});
|
||||
|
||||
// ===== ORDENAMIENTO =====
|
||||
const sortSelect = document.getElementById('sort-select');
|
||||
if (sortSelect) {
|
||||
sortSelect.addEventListener('change', (e) => {
|
||||
currentFilters.sort = e.target.value;
|
||||
sortArticles();
|
||||
});
|
||||
}
|
||||
|
||||
// ===== RESET =====
|
||||
const resetFilters = document.getElementById('reset-filters');
|
||||
if (resetFilters) {
|
||||
resetFilters.addEventListener('click', () => {
|
||||
currentFilters = { search: '', category: 'all', featured: 'all', sort: 'recent' };
|
||||
searchInput.value = '';
|
||||
searchClear?.classList.remove('visible');
|
||||
|
||||
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
|
||||
document.querySelector('.filter-btn[data-filter="all"]')?.classList.add('active');
|
||||
|
||||
document.querySelectorAll('.category-btn').forEach(b => b.classList.remove('active'));
|
||||
document.querySelector('.category-btn[data-category="all"]')?.classList.add('active');
|
||||
|
||||
if (sortSelect) sortSelect.value = 'recent';
|
||||
|
||||
filterArticles();
|
||||
});
|
||||
}
|
||||
|
||||
// ===== FUNCIÓN DE FILTRADO =====
|
||||
const filterArticles = () => {
|
||||
const articles = document.querySelectorAll('.blog-card, .featured-card');
|
||||
function filterArticles() {
|
||||
let visibleCount = 0;
|
||||
|
||||
articles.forEach(article => {
|
||||
const categories = article.dataset.categories || '';
|
||||
const tags = article.dataset.tags || '';
|
||||
const isFeatured = article.dataset.featured === 'true';
|
||||
allCards.forEach(card => {
|
||||
const categories = card.dataset.categories || '';
|
||||
const isFeatured = card.dataset.featured === 'true';
|
||||
const title = card.querySelector('.featured-title, .card-title')?.textContent.toLowerCase() || '';
|
||||
const excerpt = card.querySelector('.featured-excerpt, .card-excerpt')?.textContent.toLowerCase() || '';
|
||||
|
||||
const title = article.querySelector('.card-title, .featured-title')?.textContent.toLowerCase() || '';
|
||||
const excerpt = article.querySelector('.card-excerpt, .featured-excerpt')?.textContent.toLowerCase() || '';
|
||||
// 1. Filtro de categoría
|
||||
const matchCategory = activeCategory === 'all' || categories.includes(activeCategory);
|
||||
|
||||
const searchTerm = currentFilters.search.toLowerCase();
|
||||
const matchSearch = searchTerm === '' ||
|
||||
title.includes(searchTerm) ||
|
||||
excerpt.includes(searchTerm) ||
|
||||
categories.toLowerCase().includes(searchTerm) ||
|
||||
tags.toLowerCase().includes(searchTerm);
|
||||
// 2. Filtro de tipo (Todos / Destacados)
|
||||
const matchFilter = activeFilter === 'all' || (activeFilter === 'destacados' && isFeatured);
|
||||
|
||||
const matchCategory = currentFilters.category === 'all' ||
|
||||
categories.toLowerCase().includes(currentFilters.category.toLowerCase());
|
||||
// 3. Filtro de búsqueda
|
||||
const matchSearch = searchQuery === '' || title.includes(searchQuery) || excerpt.includes(searchQuery);
|
||||
|
||||
let matchFeatured = true;
|
||||
if (currentFilters.featured === 'destacados') {
|
||||
matchFeatured = isFeatured;
|
||||
}
|
||||
|
||||
if (matchSearch && matchCategory && matchFeatured) {
|
||||
article.style.display = '';
|
||||
if (matchCategory && matchFilter && matchSearch) {
|
||||
card.style.display = 'flex';
|
||||
visibleCount++;
|
||||
} else {
|
||||
article.style.display = 'none';
|
||||
card.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
sortArticles();
|
||||
updateResultsCount(visibleCount);
|
||||
// Actualizar contador y estado vacío
|
||||
if (resultsCount) {
|
||||
resultsCount.innerHTML = `Mostrando <strong>${visibleCount}</strong> artículo${visibleCount !== 1 ? 's' : ''}`;
|
||||
}
|
||||
|
||||
if (emptyState) {
|
||||
emptyState.style.display = visibleCount === 0 ? 'block' : 'none';
|
||||
}
|
||||
};
|
||||
|
||||
const sortArticles = () => {
|
||||
const articles = Array.from(document.querySelectorAll('.blog-card'));
|
||||
const container = blogGrid;
|
||||
|
||||
switch(currentFilters.sort) {
|
||||
case 'recent':
|
||||
articles.sort((a, b) => {
|
||||
const dateA = new Date(a.querySelector('time')?.datetime || 0);
|
||||
const dateB = new Date(b.querySelector('time')?.datetime || 0);
|
||||
return dateB - dateA;
|
||||
});
|
||||
break;
|
||||
case 'alphabetical':
|
||||
articles.sort((a, b) => {
|
||||
const titleA = a.querySelector('.card-title')?.textContent || '';
|
||||
const titleB = b.querySelector('.card-title')?.textContent || '';
|
||||
return titleA.localeCompare(titleB);
|
||||
});
|
||||
break;
|
||||
// Mostrar/ocultar botón de limpiar búsqueda
|
||||
if (searchClear) {
|
||||
searchClear.classList.toggle('visible', searchQuery.length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
articles.forEach(article => container.appendChild(article));
|
||||
};
|
||||
// Event Listeners
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
searchQuery = e.target.value.toLowerCase().trim();
|
||||
filterArticles();
|
||||
});
|
||||
}
|
||||
|
||||
const updateResultsCount = (count) => {
|
||||
if (!resultsCount) return;
|
||||
if (searchClear) {
|
||||
searchClear.addEventListener('click', () => {
|
||||
searchInput.value = '';
|
||||
searchQuery = '';
|
||||
searchInput.focus();
|
||||
filterArticles();
|
||||
});
|
||||
}
|
||||
|
||||
if (count === 0) {
|
||||
resultsCount.innerHTML = 'No se encontraron artículos';
|
||||
} else if (currentFilters.category === 'all' && currentFilters.search === '') {
|
||||
resultsCount.innerHTML = `Mostrando <strong>${count}</strong> artículos`;
|
||||
} else {
|
||||
resultsCount.innerHTML = `Mostrando <strong>${count}</strong> artículo${count !== 1 ? 's' : ''}`;
|
||||
}
|
||||
};
|
||||
categoryBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
categoryBtns.forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
activeCategory = btn.dataset.category;
|
||||
filterArticles();
|
||||
});
|
||||
});
|
||||
|
||||
// Inicializar
|
||||
filterBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
filterBtns.forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
activeFilter = btn.dataset.filter;
|
||||
filterArticles();
|
||||
});
|
||||
});
|
||||
|
||||
if (resetBtn) {
|
||||
resetBtn.addEventListener('click', () => {
|
||||
activeCategory = 'all';
|
||||
activeFilter = 'all';
|
||||
searchQuery = '';
|
||||
if (searchInput) searchInput.value = '';
|
||||
|
||||
categoryBtns.forEach(b => b.classList.remove('active'));
|
||||
document.querySelector('.category-btn[data-category="all"]')?.classList.add('active');
|
||||
|
||||
filterBtns.forEach(b => b.classList.remove('active'));
|
||||
document.querySelector('.filter-btn[data-filter="all"]')?.classList.add('active');
|
||||
|
||||
filterArticles();
|
||||
});
|
||||
}
|
||||
|
||||
// Ejecutar al inicio
|
||||
filterArticles();
|
||||
});
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Envío AJAX del formulario de contacto + modal de éxito.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const form = document.getElementById('form-contacto');
|
||||
const modal = document.getElementById('modal-contacto');
|
||||
const btnCerrar = modal ? modal.querySelector('.modal-cerrar') : null;
|
||||
const btnEnviar = form ? form.querySelector('button[type="submit"]') : null;
|
||||
|
||||
if (!form || !modal) return;
|
||||
|
||||
let redirectTimer = null;
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────────
|
||||
function abrirModal(mensaje) {
|
||||
const texto = modal.querySelector('.modal-mensaje');
|
||||
if (texto && mensaje) texto.textContent = mensaje;
|
||||
modal.classList.add('is-visible');
|
||||
modal.setAttribute('aria-hidden', 'false');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function cerrarModal(redirigir = false) {
|
||||
modal.classList.remove('is-visible');
|
||||
modal.setAttribute('aria-hidden', 'true');
|
||||
document.body.style.overflow = '';
|
||||
if (redirectTimer) clearTimeout(redirectTimer);
|
||||
if (redirigir) window.location.href = 'inicio.html';
|
||||
}
|
||||
|
||||
function mostrarError(mensaje) {
|
||||
// Reutilizamos el modal con estética distinta
|
||||
modal.classList.add('is-error');
|
||||
abrirModal(mensaje);
|
||||
}
|
||||
|
||||
// ── Cerrar modal ───────────────────────────────────────────────────
|
||||
if (btnCerrar) {
|
||||
btnCerrar.addEventListener('click', () => cerrarModal(true));
|
||||
}
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target === modal) cerrarModal(true);
|
||||
});
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && modal.classList.contains('is-visible')) {
|
||||
cerrarModal(true);
|
||||
}
|
||||
});
|
||||
|
||||
// ── Submit ─────────────────────────────────────────────────────────
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Validación nativa HTML5
|
||||
if (!form.checkValidity()) {
|
||||
form.reportValidity();
|
||||
return;
|
||||
}
|
||||
|
||||
// Estado de carga
|
||||
const textoOriginal = btnEnviar ? btnEnviar.innerHTML : '';
|
||||
if (btnEnviar) {
|
||||
btnEnviar.disabled = true;
|
||||
btnEnviar.innerHTML = 'Enviando…';
|
||||
}
|
||||
|
||||
try {
|
||||
const data = new FormData(form);
|
||||
const res = await fetch('enviar.php', {
|
||||
method: 'POST',
|
||||
body: data,
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
});
|
||||
|
||||
const json = await res.json().catch(() => ({
|
||||
status: 'error',
|
||||
message: 'Respuesta inválida del servidor.',
|
||||
}));
|
||||
|
||||
if (json.status === 'success') {
|
||||
modal.classList.remove('is-error');
|
||||
abrirModal(json.message || '¡Gracias! Te contactaremos en breve.');
|
||||
form.reset();
|
||||
|
||||
const delay = Number(json.delay) || 4000;
|
||||
redirectTimer = setTimeout(() => {
|
||||
window.location.href = json.redirect || 'inicio.html';
|
||||
}, delay);
|
||||
} else {
|
||||
mostrarError(json.message || 'No pudimos enviar tu mensaje.');
|
||||
}
|
||||
} catch (err) {
|
||||
mostrarError('Hubo un problema de conexión. Probá nuevamente.');
|
||||
} finally {
|
||||
if (btnEnviar) {
|
||||
btnEnviar.disabled = false;
|
||||
btnEnviar.innerHTML = textoOriginal;
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
+133
@@ -839,6 +839,128 @@
|
||||
color: var(--brand-teal);
|
||||
border-color: #ffffff;
|
||||
}
|
||||
/* ============================================
|
||||
PÁGINA DE ARTÍCULO INDIVIDUAL (articulo.php)
|
||||
============================================ */
|
||||
.articulo-header {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: calc(var(--spacing-lg) + 2rem) var(--spacing-md) var(--spacing-md);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.articulo-header .badge-filosofia {
|
||||
display: inline-block;
|
||||
padding: 0.4rem 1rem;
|
||||
border-radius: 999px;
|
||||
background-color: var(--badge-bg);
|
||||
color: var(--badge-text);
|
||||
font-family: var(--font-small);
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
letter-spacing: 0.5px;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.articulo-header h1 {
|
||||
font-family: var(--font-title);
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-size: clamp(2rem, 5vw, 3.5rem);
|
||||
line-height: 1.15;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.articulo-meta {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-md);
|
||||
color: var(--text-muted);
|
||||
font-family: var(--font-small);
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: var(--spacing-md);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.articulo-imagen {
|
||||
width: 100%;
|
||||
max-height: 500px;
|
||||
object-fit: cover;
|
||||
border-radius: 16px;
|
||||
margin-top: var(--spacing-md);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.articulo-contenido {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-lg) var(--spacing-md);
|
||||
font-family: var(--font-body);
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.8;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.articulo-contenido h2,
|
||||
.articulo-contenido h3 {
|
||||
font-family: var(--font-title);
|
||||
font-style: italic;
|
||||
color: var(--text-primary);
|
||||
margin-top: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.articulo-contenido p {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.articulo-contenido ul,
|
||||
.articulo-contenido ol {
|
||||
margin-bottom: 1.5rem;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.articulo-contenido li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.articulo-contenido img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 12px;
|
||||
margin: 2rem 0;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.articulo-contenido blockquote {
|
||||
border-left: 4px solid var(--brand-crimson);
|
||||
padding-left: var(--spacing-md);
|
||||
font-style: italic;
|
||||
color: var(--text-secondary);
|
||||
margin: 2rem 0;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.volver-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: var(--brand-crimson);
|
||||
text-decoration: none;
|
||||
font-family: var(--font-small);
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
margin-bottom: var(--spacing-md);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.volver-link:hover {
|
||||
color: var(--brand-teal);
|
||||
gap: 0.7rem;
|
||||
}
|
||||
|
||||
|
||||
/* ============================================
|
||||
RESPONSIVE BLOG
|
||||
@@ -855,6 +977,17 @@
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.articulo-header {
|
||||
padding: var(--spacing-lg) var(--spacing-sm) var(--spacing-sm);
|
||||
}
|
||||
.articulo-contenido {
|
||||
padding: var(--spacing-md) var(--spacing-sm);
|
||||
font-size: 1rem;
|
||||
}
|
||||
.articulo-meta {
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.blog-hero-compact {
|
||||
padding: var(--spacing-lg) var(--spacing-sm) var(--spacing-md);
|
||||
}
|
||||
|
||||
@@ -420,6 +420,13 @@
|
||||
color: var(--text-muted);
|
||||
line-height: 1.6;
|
||||
}
|
||||
.form-honeypot {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
RESPONSIVE CONTACTO
|
||||
|
||||
@@ -156,6 +156,20 @@ ul { list-style: none; }
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.badge-filosofia {
|
||||
display: inline-block;
|
||||
padding: 0.4rem 1rem;
|
||||
border-radius: 999px;
|
||||
color: var(--badge-text);
|
||||
font-family: var(--font-small);
|
||||
font-weight: 400;
|
||||
font-size: 1rem;
|
||||
letter-spacing: 0.5px;
|
||||
text-transform: uppercase;
|
||||
opacity: 0;
|
||||
animation: fadeInUp 0.9s ease-out 0s forwards;
|
||||
}
|
||||
|
||||
.site-header {
|
||||
background-color: var(--bg-surface);
|
||||
border-bottom: 1px solid var(--border);
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/* ── Modal de contacto ──────────────────────────────────────────────── */
|
||||
#modal-contacto {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(20, 18, 15, 0.55);
|
||||
backdrop-filter: blur(4px);
|
||||
-webkit-backdrop-filter: blur(4px);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity .35s ease, visibility .35s ease;
|
||||
z-index: 9999;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
#modal-contacto.is-visible {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#modal-contacto .modal-card {
|
||||
background: #fff;
|
||||
color: #1c1a17;
|
||||
border-radius: 14px;
|
||||
padding: 2.5rem 2rem;
|
||||
max-width: 460px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
box-shadow: 0 24px 60px rgba(0, 0, 0, .25);
|
||||
transform: translateY(20px) scale(.97);
|
||||
transition: transform .35s cubic-bezier(.2, .8, .2, 1);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#modal-contacto.is-visible .modal-card {
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
#modal-contacto .modal-icono {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
background: #f1ece1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 1.25rem;
|
||||
font-size: 1.75rem;
|
||||
color: #8b7355;
|
||||
}
|
||||
|
||||
#modal-contacto .modal-titulo {
|
||||
font-size: 1.4rem;
|
||||
margin: 0 0 .75rem;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
#modal-contacto .modal-mensaje {
|
||||
font-size: .98rem;
|
||||
line-height: 1.55;
|
||||
color: #4a453d;
|
||||
margin: 0 0 1.75rem;
|
||||
}
|
||||
|
||||
#modal-contacto .modal-cerrar {
|
||||
display: inline-block;
|
||||
background: #1c1a17;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: .85rem 1.75rem;
|
||||
border-radius: 999px;
|
||||
font-size: .95rem;
|
||||
cursor: pointer;
|
||||
transition: background .25s ease, transform .25s ease;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
#modal-contacto .modal-cerrar:hover {
|
||||
background: #8b7355;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* Variante error */
|
||||
#modal-contacto.is-error .modal-icono {
|
||||
background: #fdecec;
|
||||
color: #c0392b;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
#modal-contacto .modal-card { padding: 2rem 1.25rem; }
|
||||
#modal-contacto .modal-titulo { font-size: 1.2rem; }
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* Funciones de sanitización y validación reutilizables.
|
||||
*/
|
||||
|
||||
function limpiar_texto($valor, $max = 500) {
|
||||
$valor = trim((string)$valor);
|
||||
$valor = strip_tags($valor);
|
||||
$valor = preg_replace('/[\r\n\t]+/', ' ', $valor);
|
||||
if (mb_strlen($valor) > $max) {
|
||||
$valor = mb_substr($valor, 0, $max);
|
||||
}
|
||||
return $valor;
|
||||
}
|
||||
|
||||
function limpiar_mensaje($valor, $max = 5000) {
|
||||
$valor = trim((string)$valor);
|
||||
$valor = strip_tags($valor);
|
||||
if (mb_strlen($valor) > $max) {
|
||||
$valor = mb_substr($valor, 0, $max);
|
||||
}
|
||||
return $valor;
|
||||
}
|
||||
|
||||
function limpiar_email($valor) {
|
||||
return filter_var(trim((string)$valor), FILTER_SANITIZE_EMAIL);
|
||||
}
|
||||
|
||||
function email_valido($valor) {
|
||||
return (bool) filter_var($valor, FILTER_VALIDATE_EMAIL);
|
||||
}
|
||||
|
||||
function en_lista($valor, array $permitidos) {
|
||||
return in_array($valor, $permitidos, true) ? $valor : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Valida los datos del formulario y devuelve
|
||||
* ['ok' => bool, 'errores' => [], 'datos' => []]
|
||||
*/
|
||||
function validar_contacto(array $post) {
|
||||
$errores = [];
|
||||
|
||||
$datos = [
|
||||
'nombre' => limpiar_texto($post['nombre'] ?? '', 120),
|
||||
'empresa' => limpiar_texto($post['empresa'] ?? '', 120),
|
||||
'email' => limpiar_email($post['email'] ?? ''),
|
||||
'telefono' => limpiar_texto($post['telefono'] ?? '', 40),
|
||||
'servicio' => en_lista($post['servicio'] ?? '', [
|
||||
'branding','web','rrss','automatizaciones','paid','ecosistema','otro'
|
||||
]),
|
||||
'etapa' => en_lista($post['etapa'] ?? '', [
|
||||
'idea','inicio','marcha','redisenio','escalar'
|
||||
]),
|
||||
'presupuesto' => en_lista($post['presupuesto'] ?? '', [
|
||||
'esencial','impulso','premium','elite','proyecto','no-se'
|
||||
]),
|
||||
'mensaje' => limpiar_mensaje($post['mensaje'] ?? '', 5000),
|
||||
'origen' => en_lista($post['origen'] ?? '', [
|
||||
'instagram','linkedin','referido','google','otro'
|
||||
]),
|
||||
'consentimiento'=> !empty($post['consentimiento']),
|
||||
];
|
||||
|
||||
if ($datos['nombre'] === '') $errores[] = 'El nombre es obligatorio.';
|
||||
if (!email_valido($datos['email'])) $errores[] = 'El email no es válido.';
|
||||
if ($datos['servicio'] === '') $errores[] = 'Seleccioná un servicio de interés.';
|
||||
if ($datos['mensaje'] === '') $errores[] = 'El mensaje es obligatorio.';
|
||||
if (!$datos['consentimiento']) $errores[] = 'Debés aceptar el uso de tus datos.';
|
||||
|
||||
return [
|
||||
'ok' => empty($errores),
|
||||
'errores' => $errores,
|
||||
'datos' => $datos,
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user