Complete web sections implementation, top banner and update live submodule

This commit is contained in:
Carlos (Almeira) 2026-07-06 21:26:37 -05:00
parent 03d686d443
commit bf3c472894
10 changed files with 132 additions and 26 deletions

@ -1 +1 @@
Subproject commit 07ecfa5268f8b17294a25d08ed82340479f4aaae
Subproject commit 4ea666b69d9f37cb0589be5659572a7ea6440bd0

78
publish-dummies.js Normal file
View File

@ -0,0 +1,78 @@
const sdk = require('node-appwrite');
require('dotenv').config({ path: '.env.local' });
const client = new sdk.Client();
const databases = new sdk.Databases(client);
const ENDPOINT = process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT || 'https://appwrite.almeira.com.co/v1';
const PROJECT_ID = process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID || '69b84df5003bd8957c5c';
const API_KEY = process.env.APPWRITE_API_KEY;
client
.setEndpoint(ENDPOINT)
.setProject(PROJECT_ID)
.setKey(API_KEY);
const dbId = process.env.APPWRITE_DATABASE_ID || 'almeira';
async function publishDummies() {
try {
console.log('Obteniendo tipos de publicación...');
const typesRes = await databases.listDocuments(dbId, 'publication_types');
const types = typesRes.documents;
const typeClasif = types.find(t => t.codigo === 'CLASIFICADO');
const typeEvento = types.find(t => t.codigo === 'EVENTO');
const typeBanner = types.find(t => t.codigo === 'DISPLAY_BANNER');
if (!typeClasif || !typeEvento || !typeBanner) {
console.log('Faltan algunos tipos de publicación en la base de datos.');
return;
}
console.log('Insertando Clasificado de prueba...');
await databases.createDocument(dbId, 'posts', sdk.ID.unique(), {
titulo: 'Vendo Hermosa Casa en el Centro',
slug: 'vendo-hermosa-casa-centro-' + Date.now(),
resumen_redes: 'Oportunidad única para adquirir una casa con excelentes acabados y ubicación estratégica.',
contenido: '<p>Oportunidad única para adquirir una casa con excelentes acabados y ubicación estratégica. 3 habitaciones, 2 baños, garaje.</p>',
estado: 'Publicado',
fecha_publicacion: new Date().toISOString(),
tipo_publicacion_id: typeClasif.$id,
og_image_url: 'https://images.unsplash.com/photo-1512917774080-9991f1c4c750?q=80&w=1000&auto=format&fit=crop'
});
console.log('Insertando Evento de prueba...');
await databases.createDocument(dbId, 'posts', sdk.ID.unique(), {
titulo: 'Gran Concierto de Fin de Año',
slug: 'gran-concierto-fin-de-ano-' + Date.now(),
resumen_redes: 'No te pierdas el evento musical más esperado del año con artistas locales e internacionales.',
contenido: '<p>No te pierdas el evento musical más esperado del año con artistas locales e internacionales. Entrada libre.</p>',
estado: 'Publicado',
fecha_publicacion: new Date().toISOString(),
fecha_finalizacion: new Date(Date.now() + 86400000 * 30).toISOString(), // + 30 days
tipo_publicacion_id: typeEvento.$id,
og_image_url: 'https://images.unsplash.com/photo-1540039155733-d76e6148e18e?q=80&w=1000&auto=format&fit=crop'
});
console.log('Insertando Banner de prueba...');
await databases.createDocument(dbId, 'posts', sdk.ID.unique(), {
titulo: 'Gran Promoción Almeira',
slug: 'banner-promo-almeira-' + Date.now(),
contenido: '', // Requerido por el schema de posts
estado: 'Publicado',
fecha_publicacion: new Date().toISOString(),
fecha_finalizacion: new Date(Date.now() + 86400000 * 30).toISOString(),
tipo_publicacion_id: typeBanner.$id,
url_destino: 'https://almeira.com.co',
og_image_url: 'https://images.unsplash.com/photo-1504222490345-c075b6008014?q=80&w=1000&auto=format&fit=crop'
});
console.log('¡Publicaciones de prueba insertadas con éxito!');
} catch (e) {
console.error('Error insertando dummies:', e);
}
}
publishDummies();

View File

@ -1,5 +1,5 @@
import { APPWRITE_ALMEIRA_CONFIG } from '@/lib/appwrite';
import { createAdminClient } from '@/lib/appwrite.server';
import { createAdminClient, getLoggedInUser } from '@/lib/appwrite.server';
import { Query } from 'node-appwrite';
import { NewsCard } from '@/components/NewsCard';
import { GlobalSidebar, hasSidebarContent } from '@/components/AdWrappers';
@ -40,11 +40,16 @@ async function fetchClasificadosData() {
if (!typeId) return { posts: [], cardSettings };
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
Query.equal('tipo_publicacion_id', typeId),
Query.equal('estado', 'Publicado'),
Query.equal('estado', ['Publicado', 'publicado']),
Query.orderDesc('fecha_publicacion'),
Query.limit(postsCount)
Query.limit(postsCount * 2)
]);
posts = postsRes.documents as unknown as Post[];
const now = new Date();
posts = postsRes.documents.filter((post: any) => {
if (!post.fecha_finalizacion) return true;
return new Date(post.fecha_finalizacion) >= now;
}).slice(0, postsCount) as unknown as Post[];
// 3. Resolve Media
const mediaIds = posts.map(p => p.imagen_destacada_id).filter(id => id && typeof id === 'string') as string[];
@ -73,6 +78,9 @@ export default async function ClasificadosPage() {
const { posts, cardSettings } = await fetchClasificadosData();
const hasSidebar = await hasSidebarContent();
const user = await getLoggedInUser();
const isAdmin = (user as any)?.labels?.includes('admin') || (user as any)?.labels?.includes('editor') || user?.$id === 'admin-emergency';
return (
<main className="animate-fade-in relative min-h-screen">
{/* Sci-Fi Hero Section */}
@ -128,8 +136,10 @@ export default async function ClasificadosPage() {
const ENDPOINT = process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT || 'https://appwrite.almeira.com.co/v1';
let imagenUrl = post.og_image_url || null;
if (post.resolved_media) {
imagenUrl = `${ENDPOINT}/storage/buckets/${post.resolved_media.bucket_id}/files/${post.resolved_media.file_id}/preview?project=${PROJECT_ID}&width=800&height=500&quality=85`;
const mediaObj = post.resolved_media || (typeof post.imagen_destacada_id === 'object' ? post.imagen_destacada_id : null) as MediaItem | null;
if (mediaObj) {
imagenUrl = `${ENDPOINT}/storage/buckets/${mediaObj.bucket_id}/files/${mediaObj.file_id}/preview?project=${PROJECT_ID}&width=800&height=800&quality=85`;
}
return (
@ -146,6 +156,8 @@ export default async function ClasificadosPage() {
showFooterDate={cardSettings.showFooterDate}
showFooterButton={cardSettings.showFooterButton}
footerText={cardSettings.footerText}
imageAspect="aspect-square"
isAdmin={isAdmin}
/>
)
})}

View File

@ -1,5 +1,5 @@
import { APPWRITE_ALMEIRA_CONFIG } from '@/lib/appwrite';
import { createAdminClient } from '@/lib/appwrite.server';
import { createAdminClient, getLoggedInUser } from '@/lib/appwrite.server';
import { Query } from 'node-appwrite';
import { NewsCard } from '@/components/NewsCard';
import { GlobalSidebar, hasSidebarContent } from '@/components/AdWrappers';
@ -40,11 +40,16 @@ async function fetchEventosData() {
if (!typeId) return { posts: [], cardSettings };
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
Query.equal('tipo_publicacion_id', typeId),
Query.equal('estado', 'Publicado'),
Query.equal('estado', ['Publicado', 'publicado']),
Query.orderDesc('fecha_publicacion'),
Query.limit(postsCount)
Query.limit(postsCount * 2)
]);
posts = postsRes.documents as unknown as Post[];
const now = new Date();
posts = postsRes.documents.filter((post: any) => {
if (!post.fecha_finalizacion) return true;
return new Date(post.fecha_finalizacion) >= now;
}).slice(0, postsCount) as unknown as Post[];
// 3. Resolve Media
const mediaIds = posts.map(p => p.imagen_destacada_id).filter(id => id && typeof id === 'string') as string[];
@ -73,6 +78,9 @@ export default async function EventosPage() {
const { posts, cardSettings } = await fetchEventosData();
const hasSidebar = await hasSidebarContent();
const user = await getLoggedInUser();
const isAdmin = (user as any)?.labels?.includes('admin') || (user as any)?.labels?.includes('editor') || user?.$id === 'admin-emergency';
return (
<main className="animate-fade-in relative min-h-screen">
{/* Sci-Fi Hero Section */}
@ -128,8 +136,10 @@ export default async function EventosPage() {
const ENDPOINT = process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT || 'https://appwrite.almeira.com.co/v1';
let imagenUrl = post.og_image_url || null;
if (post.resolved_media) {
imagenUrl = `${ENDPOINT}/storage/buckets/${post.resolved_media.bucket_id}/files/${post.resolved_media.file_id}/preview?project=${PROJECT_ID}&width=800&height=500&quality=85`;
const mediaObj = post.resolved_media || (typeof post.imagen_destacada_id === 'object' ? post.imagen_destacada_id : null) as MediaItem | null;
if (mediaObj) {
imagenUrl = `${ENDPOINT}/storage/buckets/${mediaObj.bucket_id}/files/${mediaObj.file_id}/preview?project=${PROJECT_ID}&width=800&height=800&quality=85`;
}
return (
@ -146,6 +156,8 @@ export default async function EventosPage() {
showFooterDate={cardSettings.showFooterDate}
showFooterButton={cardSettings.showFooterButton}
footerText={cardSettings.footerText}
imageAspect="aspect-square"
isAdmin={isAdmin}
/>
)
})}

View File

@ -53,7 +53,7 @@ async function fetchHomeData(page: number = 1, isAdmin: boolean = false, adminMo
];
if (isAdmin) {
queries.push(Query.equal('estado', ['Publicado', 'borrador']));
queries.push(Query.equal('estado', ['Publicado', 'publicado', 'borrador']));
if (adminMonth) {
const currentYear = new Date().getFullYear();
// First day of the selected month
@ -64,7 +64,7 @@ async function fetchHomeData(page: number = 1, isAdmin: boolean = false, adminMo
queries.push(Query.lessThan('fecha_publicacion', endDate));
}
} else {
queries.push(Query.equal('estado', 'Publicado'));
queries.push(Query.equal('estado', ['Publicado', 'publicado']));
}
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', queries);
@ -216,8 +216,10 @@ export default async function Home(props: { searchParams: Promise<{ [key: string
const ENDPOINT = process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT || 'https://appwrite.almeira.com.co/v1';
let imagenUrl = post.og_image_url || null;
if (post.resolved_media) {
imagenUrl = `${ENDPOINT}/storage/buckets/${post.resolved_media.bucket_id}/files/${post.resolved_media.file_id}/preview?project=${PROJECT_ID}&width=800&height=500&quality=85`;
const mediaObj = post.resolved_media || (typeof post.imagen_destacada_id === 'object' ? post.imagen_destacada_id : null) as MediaItem | null;
if (mediaObj) {
imagenUrl = `${ENDPOINT}/storage/buckets/${mediaObj.bucket_id}/files/${mediaObj.file_id}/preview?project=${PROJECT_ID}&width=800&height=500&quality=85`;
}
return (

View File

@ -22,7 +22,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
// Fetch all published posts
const postsRes = await client.databases.listDocuments(dbId, 'posts', [
Query.equal('estado', 'Publicado'),
Query.equal('estado', ['Publicado', 'publicado']),
Query.limit(1000), // Get up to 1000 posts. For more, pagination is needed.
Query.orderDesc('fecha_publicacion')
]);

View File

@ -33,7 +33,7 @@ export async function GlobalTopBanner() {
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
Query.equal('tipo_publicacion_id', typeId),
Query.equal('estado', 'Publicado'),
Query.equal('estado', ['Publicado', 'publicado']),
Query.orderDesc('fecha_publicacion'),
Query.limit(20)
]);
@ -82,7 +82,7 @@ export async function GlobalSidebar() {
if (validTypeIds.length > 0) {
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
Query.equal('estado', 'Publicado'),
Query.equal('estado', ['Publicado', 'publicado']),
Query.orderAsc('fecha_finalizacion'),
Query.limit(50)
]);
@ -135,7 +135,7 @@ export async function hasSidebarContent(): Promise<boolean> {
if (validTypeIds.length === 0) return false;
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
Query.equal('estado', 'Publicado'),
Query.equal('estado', ['Publicado', 'publicado']),
Query.limit(20)
]);

View File

@ -15,6 +15,7 @@ interface NewsCardProps {
showFooterButton?: boolean;
footerText?: string;
isAdmin?: boolean;
imageAspect?: string;
}
export const NewsCard = ({
@ -29,7 +30,8 @@ export const NewsCard = ({
showFooterDate = false,
showFooterButton = false,
footerText,
isAdmin = false
isAdmin = false,
imageAspect = 'aspect-[16/10]'
}: NewsCardProps) => {
const titulo = decodeHtmlEntities(rawTitulo);
const resumen = decodeHtmlEntities(rawResumen);
@ -59,7 +61,7 @@ export const NewsCard = ({
)}
{/* Imagen Destacada */}
<div className="relative aspect-[16/10] w-full overflow-hidden bg-slate-800 z-0">
<div className={`relative ${imageAspect} w-full overflow-hidden bg-slate-800 z-0`}>
<div className="absolute inset-0 bg-gradient-to-t from-slate-950 via-slate-950/20 to-transparent z-10 opacity-90 pointer-events-none"></div>
{imagenUrl ? (
<img src={imagenUrl} alt={titulo} className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110" />

View File

@ -41,7 +41,7 @@ export default async function NewsTicker() {
if (!typeId) throw new Error("Could not find typeId for NOTICIA");
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
Query.equal('tipo_publicacion_id', typeId),
Query.equal('estado', 'Publicado'),
Query.equal('estado', ['Publicado', 'publicado']),
Query.orderDesc('fecha_publicacion'),
Query.limit(tickerCount)
]);
@ -57,7 +57,7 @@ export default async function NewsTicker() {
}
postsRes.documents.forEach((post: any) => {
const media = mediaMap.get(post.imagen_destacada_id);
const media = typeof post.imagen_destacada_id === 'object' ? post.imagen_destacada_id : mediaMap.get(post.imagen_destacada_id);
let imagenUrl = post.og_image_url || null;
if (media) {
imagenUrl = `${APPWRITE_ALMEIRA_CONFIG.endpoint}/storage/buckets/${media.bucket_id}/files/${media.file_id}/preview?project=${APPWRITE_ALMEIRA_CONFIG.projectId}`;

View File

@ -22,7 +22,7 @@ export default function TopBanner({ initialBanners, rotationSeconds }: { initial
const activeBanner = initialBanners[currentIndex];
if (!activeBanner.imagen_destacada_id) return null;
if (!activeBanner.imagen_destacada_id && !activeBanner.og_image_url) return null;
const ENDPOINT = process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT || 'https://appwrite.almeira.com.co/v1';
const PROJECT_ID = process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID || '69b84df5003bd8957c5c';