Fix: Refactor feeds to Server Components, fix filters/counts, and add news detail route

This commit is contained in:
Carlos (Almeira) 2026-03-26 14:29:16 -05:00
parent 5e7a28878f
commit 43dd3770b9
4 changed files with 288 additions and 155 deletions

View File

@ -1,36 +1,43 @@
"use client"; import { APPWRITE_ALMEIRA_CONFIG } from '@/lib/appwrite';
import { createAdminClient } from '@/lib/appwrite.server';
import { useState, useEffect } from 'react'; import { Query } from 'node-appwrite';
import { databases, APPWRITE_ALMEIRA_CONFIG, Query } from '@/lib/appwrite';
import { NewsCard } from '@/components/NewsCard'; import { NewsCard } from '@/components/NewsCard';
import { Loader2 } from 'lucide-react'; import { Loader2, Sparkles } from 'lucide-react';
export default function ClasificadosPage() { async function fetchClasificadosData() {
const [posts, setPosts] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchLatestPosts = async () => {
try { try {
const response = await databases.listDocuments( const client = await createAdminClient();
APPWRITE_ALMEIRA_CONFIG.databaseId,
APPWRITE_ALMEIRA_CONFIG.collections.posts,
[
Query.equal('tipo', 'clasificado'),
Query.orderDesc('$createdAt'),
Query.limit(6)
]
);
setPosts(response.documents);
} catch (error) {
console.error("Error fetching posts:", error);
} finally {
setIsLoading(false);
}
};
fetchLatestPosts(); // 1. Settings for count
}, []); const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []);
const postsCount = settingsRes.documents.length > 0 ? (settingsRes.documents[0].classifieds_count || 9) : 9;
// 2. Get CLASIFICADO type ID
const typeRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'publication_types', [
Query.equal('codigo', 'CLASIFICADO')
]);
let posts: any[] = [];
if (typeRes.documents.length > 0) {
const typeId = typeRes.documents[0].$id;
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
Query.equal('tipo_publicacion_id', typeId),
Query.equal('estado', 'Publicado'),
Query.orderDesc('fecha_publicacion'),
Query.limit(postsCount)
]);
posts = postsRes.documents;
}
return { posts };
} catch (e) {
console.error("Clasificados fetch error", e);
return { posts: [] };
}
}
export default async function ClasificadosPage() {
const { posts } = await fetchClasificadosData();
return ( return (
<main className="animate-fade-in relative min-h-screen"> <main className="animate-fade-in relative min-h-screen">
@ -49,13 +56,13 @@ export default function ClasificadosPage() {
Compra, venta y servicios en tu comunidad al instante con total seguridad. Compra, venta y servicios en tu comunidad al instante con total seguridad.
</p> </p>
<button <a
onClick={() => document.getElementById('noticias')?.scrollIntoView({ behavior: 'smooth'})} href="#noticias"
className="group relative inline-flex items-center justify-center gap-3 px-8 py-3 rounded-full font-bold text-white overflow-hidden transition-all hover:scale-105" className="group relative inline-flex items-center justify-center gap-3 px-8 py-3 rounded-full font-bold text-white overflow-hidden transition-all hover:scale-105"
> >
<div className="absolute inset-0 bg-gradient-to-r from-purple-600 to-blue-600 opacity-90 group-hover:opacity-100 transition-opacity"></div> <div className="absolute inset-0 bg-gradient-to-r from-purple-600 to-blue-600 opacity-90 group-hover:opacity-100 transition-opacity"></div>
<span className="relative z-10 tracking-widest uppercase text-sm">Explorar Mercado</span> <span className="relative z-10 tracking-widest uppercase text-sm">Explorar Mercado</span>
</button> </a>
</div> </div>
</section> </section>
@ -70,12 +77,7 @@ export default function ClasificadosPage() {
</div> </div>
</div> </div>
{isLoading ? ( {posts.length === 0 ? (
<div className="w-full py-40 flex flex-col items-center justify-center text-purple-500">
<Loader2 size={50} className="animate-spin mb-6" />
<p className="font-outfit text-xl font-bold tracking-widest uppercase animate-pulse">Sincronizando Noticias...</p>
</div>
) : posts.length === 0 ? (
<div className="w-full py-20 flex flex-col items-center justify-center border border-white/5 rounded-3xl glass-panel relative overflow-hidden"> <div className="w-full py-20 flex flex-col items-center justify-center border border-white/5 rounded-3xl glass-panel relative overflow-hidden">
<div className="p-6 rounded-full bg-slate-800/80 mb-6 border border-white/10 relative z-10"> <div className="p-6 rounded-full bg-slate-800/80 mb-6 border border-white/10 relative z-10">
<Sparkles size={48} className="text-purple-500" /> <Sparkles size={48} className="text-purple-500" />
@ -85,18 +87,27 @@ export default function ClasificadosPage() {
</div> </div>
) : ( ) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{posts.map((post) => ( {posts.map((post) => {
const PROJECT_ID = process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID || '69b84df5003bd8957c5c';
const ENDPOINT = process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT || 'https://appwrite.almeira.com.co/v1';
let imagenUrl = null;
if (post.imagen_destacada_id) {
imagenUrl = `${ENDPOINT}/storage/buckets/${post.imagen_destacada_id.bucket_id}/files/${post.imagen_destacada_id.file_id}/preview?project=${PROJECT_ID}&width=800&height=500&quality=85`;
}
return (
<NewsCard <NewsCard
key={post.$id} key={post.$id}
id={post.$id} id={post.$id}
slug={post.slug || post.$id} slug={post.slug || post.$id}
titulo={post.titulo} titulo={post.titulo}
resumen={post.resumen_redes || post.contenido?.substring(0, 100) + '...'} resumen={post.resumen_redes || post.contenido?.substring(0, 100).replace(/<[^>]*>?/gm, '') + '...'}
tipo={post.tipo} tipo="Aviso"
fecha={post.fecha_publicacion || post.$createdAt} fecha={post.fecha_publicacion || post.$createdAt}
imagenUrl={post.imagen_destacada_id || post.og_image_url} imagenUrl={imagenUrl}
/> />
))} )
})}
</div> </div>
)} )}
</section> </section>

View File

@ -1,36 +1,43 @@
"use client"; import { APPWRITE_ALMEIRA_CONFIG } from '@/lib/appwrite';
import { createAdminClient } from '@/lib/appwrite.server';
import { useState, useEffect } from 'react'; import { Query } from 'node-appwrite';
import { databases, APPWRITE_ALMEIRA_CONFIG, Query } from '@/lib/appwrite';
import { NewsCard } from '@/components/NewsCard'; import { NewsCard } from '@/components/NewsCard';
import { Loader2 } from 'lucide-react'; import { Loader2, Calendar } from 'lucide-react';
export default function EventosPage() { async function fetchEventosData() {
const [posts, setPosts] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchLatestPosts = async () => {
try { try {
const response = await databases.listDocuments( const client = await createAdminClient();
APPWRITE_ALMEIRA_CONFIG.databaseId,
APPWRITE_ALMEIRA_CONFIG.collections.posts,
[
Query.equal('tipo', 'evento'),
Query.orderDesc('$createdAt'),
Query.limit(6)
]
);
setPosts(response.documents);
} catch (error) {
console.error("Error fetching posts:", error);
} finally {
setIsLoading(false);
}
};
fetchLatestPosts(); // 1. Settings for count
}, []); const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []);
const postsCount = settingsRes.documents.length > 0 ? (settingsRes.documents[0].events_count || 9) : 9;
// 2. Get EVENTO type ID
const typeRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'publication_types', [
Query.equal('codigo', 'EVENTO')
]);
let posts: any[] = [];
if (typeRes.documents.length > 0) {
const typeId = typeRes.documents[0].$id;
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
Query.equal('tipo_publicacion_id', typeId),
Query.equal('estado', 'Publicado'),
Query.orderDesc('fecha_publicacion'),
Query.limit(postsCount)
]);
posts = postsRes.documents;
}
return { posts };
} catch (e) {
console.error("Eventos fetch error", e);
return { posts: [] };
}
}
export default async function EventosPage() {
const { posts } = await fetchEventosData();
return ( return (
<main className="animate-fade-in relative min-h-screen"> <main className="animate-fade-in relative min-h-screen">
@ -49,13 +56,13 @@ export default function EventosPage() {
Los mejores eventos artísticos, culturales y sociales en un solo lugar. Los mejores eventos artísticos, culturales y sociales en un solo lugar.
</p> </p>
<button <a
onClick={() => document.getElementById('noticias')?.scrollIntoView({ behavior: 'smooth'})} href="#noticias"
className="group relative inline-flex items-center justify-center gap-3 px-8 py-3 rounded-full font-bold text-white overflow-hidden transition-all hover:scale-105" className="group relative inline-flex items-center justify-center gap-3 px-8 py-3 rounded-full font-bold text-white overflow-hidden transition-all hover:scale-105"
> >
<div className="absolute inset-0 bg-gradient-to-r from-cyan-500 to-blue-600 opacity-90 group-hover:opacity-100 transition-opacity"></div> <div className="absolute inset-0 bg-gradient-to-r from-cyan-500 to-blue-600 opacity-90 group-hover:opacity-100 transition-opacity"></div>
<span className="relative z-10 tracking-widest uppercase text-sm">Explorar Agenda</span> <span className="relative z-10 tracking-widest uppercase text-sm">Explorar Agenda</span>
</button> </a>
</div> </div>
</section> </section>
@ -70,12 +77,7 @@ export default function EventosPage() {
</div> </div>
</div> </div>
{isLoading ? ( {posts.length === 0 ? (
<div className="w-full py-40 flex flex-col items-center justify-center text-cyan-500">
<Loader2 size={50} className="animate-spin mb-6" />
<p className="font-outfit text-xl font-bold tracking-widest uppercase animate-pulse">Sincronizando Noticias...</p>
</div>
) : posts.length === 0 ? (
<div className="w-full py-20 flex flex-col items-center justify-center border border-white/5 rounded-3xl glass-panel relative overflow-hidden"> <div className="w-full py-20 flex flex-col items-center justify-center border border-white/5 rounded-3xl glass-panel relative overflow-hidden">
<div className="p-6 rounded-full bg-slate-800/80 mb-6 border border-white/10 relative z-10"> <div className="p-6 rounded-full bg-slate-800/80 mb-6 border border-white/10 relative z-10">
<Calendar size={48} className="text-cyan-500" /> <Calendar size={48} className="text-cyan-500" />
@ -85,18 +87,27 @@ export default function EventosPage() {
</div> </div>
) : ( ) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{posts.map((post) => ( {posts.map((post) => {
const PROJECT_ID = process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID || '69b84df5003bd8957c5c';
const ENDPOINT = process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT || 'https://appwrite.almeira.com.co/v1';
let imagenUrl = null;
if (post.imagen_destacada_id) {
imagenUrl = `${ENDPOINT}/storage/buckets/${post.imagen_destacada_id.bucket_id}/files/${post.imagen_destacada_id.file_id}/preview?project=${PROJECT_ID}&width=800&height=500&quality=85`;
}
return (
<NewsCard <NewsCard
key={post.$id} key={post.$id}
id={post.$id} id={post.$id}
slug={post.slug || post.$id} slug={post.slug || post.$id}
titulo={post.titulo} titulo={post.titulo}
resumen={post.resumen_redes || post.contenido?.substring(0, 100) + '...'} resumen={post.resumen_redes || post.contenido?.substring(0, 100).replace(/<[^>]*>?/gm, '') + '...'}
tipo={post.tipo} tipo="Evento"
fecha={post.fecha_publicacion || post.$createdAt} fecha={post.fecha_publicacion || post.$createdAt}
imagenUrl={post.imagen_destacada_id || post.og_image_url} imagenUrl={imagenUrl}
/> />
))} )
})}
</div> </div>
)} )}
</section> </section>

View File

@ -0,0 +1,102 @@
import { APPWRITE_ALMEIRA_CONFIG } from '@/lib/appwrite';
import { createAdminClient } from '@/lib/appwrite.server';
import { Query } from 'node-appwrite';
import { GlobalSidebar } from '@/components/AdWrappers';
import { Calendar, User, ArrowLeft } from 'lucide-react';
import Link from 'next/link';
async function fetchPostData(slug: string) {
try {
const client = await createAdminClient();
const res = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
Query.equal('slug', slug),
Query.limit(1)
]);
if (res.documents.length === 0) return null;
return res.documents[0];
} catch (e) {
console.error("Post detail fetch error", e);
return null;
}
}
export default async function NoticiaDetail({ params }: { params: { slug: string } }) {
const post = await fetchPostData(params.slug);
if (!post) {
return (
<div className="min-h-screen flex flex-col items-center justify-center bg-slate-950 text-white p-4">
<h1 className="text-4xl font-black font-outfit mb-4">404 - SEÑAL PERDIDA</h1>
<p className="text-slate-400 mb-8 text-center">No hemos podido localizar la transmisión solicitada en el núcleo central.</p>
<Link href="/" className="px-6 py-3 rounded-full bg-cyan-600 font-bold hover:bg-cyan-500 transition-colors">VOLVER AL FEED</Link>
</div>
);
}
const PROJECT_ID = process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID || '69b84df5003bd8957c5c';
const ENDPOINT = process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT || 'https://appwrite.almeira.com.co/v1';
let imagenUrl = null;
if (post.imagen_destacada_id) {
imagenUrl = `${ENDPOINT}/storage/buckets/${post.imagen_destacada_id.bucket_id}/files/${post.imagen_destacada_id.file_id}/preview?project=${PROJECT_ID}&width=1200&height=600&quality=90`;
}
const fechaObj = new Date(post.fecha_publicacion || post.$createdAt);
const fechaFormateada = fechaObj.toLocaleDateString('es-ES', { day: '2-digit', month: 'long', year: 'numeric' });
return (
<main className="animate-fade-in relative min-h-screen pt-24 pb-20">
<div className="container mx-auto px-4 lg:px-8">
<div className="flex flex-col lg:flex-row gap-12">
<article className="flex-1 max-w-4xl overflow-hidden">
<Link href="/" className="inline-flex items-center gap-2 text-cyan-400 font-bold hover:text-cyan-300 transition-colors mb-8 group">
<ArrowLeft size={18} className="transition-transform group-hover:-translate-x-1" />
VOLVER A NOTICIAS
</Link>
<header className="mb-10">
<div className="flex items-center gap-4 mb-6">
<span className="px-3 py-1 rounded-full bg-cyan-500/10 border border-cyan-500/30 text-cyan-400 text-xs font-black tracking-widest uppercase">
{post.tipo?.toUpperCase() || 'NOTICIA'}
</span>
<div className="flex items-center gap-2 text-slate-500 text-sm font-medium">
<Calendar size={16} />
<span>{fechaFormateada}</span>
</div>
</div>
<h1 className="text-4xl md:text-6xl font-outfit font-black text-white leading-tight mb-6">
{post.titulo}
</h1>
{post.resumen_redes && (
<p className="text-xl text-slate-400 font-medium leading-relaxed italic border-l-4 border-cyan-500 pl-6 py-2">
{post.resumen_redes}
</p>
)}
</header>
{imagenUrl && (
<div className="relative aspect-[21/9] w-full rounded-3xl overflow-hidden mb-12 border border-white/10 shadow-2xl">
<img src={imagenUrl} alt={post.titulo} className="w-full h-full object-cover" />
<div className="absolute inset-0 bg-gradient-to-t from-slate-950/50 to-transparent"></div>
</div>
)}
<div
className="prose prose-invert prose-cyan max-w-none text-slate-300 leading-relaxed text-lg
prose-headings:font-outfit prose-headings:font-black prose-headings:text-white
prose-p:mb-6 prose-strong:text-cyan-400"
dangerouslySetInnerHTML={{ __html: post.contenido }}
/>
</article>
<aside className="w-full lg:w-[320px] xl:w-[360px] shrink-0 sticky top-24 h-fit">
<GlobalSidebar />
</aside>
</div>
</div>
</main>
);
}

View File

@ -1,37 +1,45 @@
"use client"; import { APPWRITE_ALMEIRA_CONFIG } from '@/lib/appwrite';
import { createAdminClient } from '@/lib/appwrite.server';
import { useState, useEffect } from 'react'; import { Query } from 'node-appwrite';
import { databases, APPWRITE_ALMEIRA_CONFIG, Query } from '@/lib/appwrite';
import { NewsCard } from '@/components/NewsCard'; import { NewsCard } from '@/components/NewsCard';
import { GlobalSidebar } from '@/components/AdWrappers'; import { GlobalSidebar } from '@/components/AdWrappers';
import { Loader2, RadioTower, Sparkles, ChevronDown } from 'lucide-react'; import { RadioTower, Sparkles, ChevronDown, Loader2 } from 'lucide-react';
import { Suspense } from 'react';
export default function Home() { async function fetchHomeData() {
const [posts, setPosts] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchLatestPosts = async () => {
try { try {
const response = await databases.listDocuments( const client = await createAdminClient();
APPWRITE_ALMEIRA_CONFIG.databaseId,
APPWRITE_ALMEIRA_CONFIG.collections.posts,
[
Query.equal('tipo', 'noticia'),
Query.orderDesc('$createdAt'),
Query.limit(6)
]
);
setPosts(response.documents);
} catch (error) {
console.error("Error fetching posts:", error);
} finally {
setIsLoading(false);
}
};
fetchLatestPosts(); // 1. Settings for count
}, []); const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []);
const postsCount = settingsRes.documents.length > 0 ? (settingsRes.documents[0].feed_count || settingsRes.documents[0].post_count || 9) : 9;
// 2. Get NOTICIA type ID
const typeRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'publication_types', [
Query.equal('codigo', 'NOTICIA')
]);
let posts: any[] = [];
if (typeRes.documents.length > 0) {
const typeId = typeRes.documents[0].$id;
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
Query.equal('tipo_publicacion_id', typeId),
Query.equal('estado', 'Publicado'),
Query.orderDesc('fecha_publicacion'),
Query.limit(postsCount)
]);
posts = postsRes.documents;
}
return { posts };
} catch (e) {
console.error("Home fetch error", e);
return { posts: [] };
}
}
export default async function Home() {
const { posts } = await fetchHomeData();
return ( return (
<main className="animate-fade-in relative"> <main className="animate-fade-in relative">
@ -56,22 +64,22 @@ export default function Home() {
El primer portal transaccional hiperconectado. Sumérgete en la actualidad de la región en tiempo real con una interfaz sin precedentes. El primer portal transaccional hiperconectado. Sumérgete en la actualidad de la región en tiempo real con una interfaz sin precedentes.
</p> </p>
<button <a
onClick={() => document.getElementById('noticias')?.scrollIntoView({ behavior: 'smooth'})} href="#noticias"
className="group relative inline-flex items-center justify-center gap-3 px-8 py-4 rounded-full font-bold text-white text-lg overflow-hidden transition-all hover:scale-105" className="group relative inline-flex items-center justify-center gap-3 px-8 py-4 rounded-full font-bold text-white text-lg overflow-hidden transition-all hover:scale-105"
> >
<div className="absolute inset-0 bg-gradient-to-r from-cyan-500 to-blue-600 opacity-90 group-hover:opacity-100 transition-opacity"></div> <div className="absolute inset-0 bg-gradient-to-r from-cyan-500 to-blue-600 opacity-90 group-hover:opacity-100 transition-opacity"></div>
<div className="absolute -inset-1 bg-cyan-400 blur-xl opacity-40 group-hover:opacity-70 transition-opacity rounded-full animate-pulse"></div> <div className="absolute -inset-1 bg-cyan-400 blur-xl opacity-40 group-hover:opacity-70 transition-opacity rounded-full animate-pulse"></div>
<Sparkles size={20} className="relative z-10" /> <Sparkles size={20} className="relative z-10" />
<span className="relative z-10 tracking-wide">EXPLORAR CONTENIDO</span> <span className="relative z-10 tracking-wide">EXPLORAR CONTENIDO</span>
</button> </a>
</div> </div>
{/* Scroll Indicator */} {/* Scroll Indicator */}
<div className="absolute bottom-10 left-1/2 -translate-x-1/2 flex flex-col items-center gap-2 text-slate-500 animate-bounce cursor-pointer hover:text-cyan-400 transition-colors" onClick={() => document.getElementById('noticias')?.scrollIntoView({ behavior: 'smooth'})}> <a href="#noticias" className="absolute bottom-10 left-1/2 -translate-x-1/2 flex flex-col items-center gap-2 text-slate-500 animate-bounce cursor-pointer hover:text-cyan-400 transition-colors">
<span className="text-xs font-bold uppercase tracking-widest">Descubrir</span> <span className="text-xs font-bold uppercase tracking-widest">Descubrir</span>
<ChevronDown size={24} className="text-cyan-500" /> <ChevronDown size={24} className="text-cyan-500" />
</div> </a>
</section> </section>
{/* Feed Section */} {/* Feed Section */}
@ -88,15 +96,7 @@ export default function Home() {
</div> </div>
</div> </div>
{isLoading ? ( {posts.length === 0 ? (
<div className="w-full py-40 flex flex-col items-center justify-center text-cyan-500">
<div className="relative">
<div className="absolute inset-0 bg-cyan-500 blur-xl opacity-40 animate-pulse rounded-full"></div>
<Loader2 size={60} className="relative animate-spin mb-6" />
</div>
<p className="font-outfit text-xl font-bold tracking-widest uppercase animate-pulse">Sincronizando Noticias...</p>
</div>
) : posts.length === 0 ? (
<div className="w-full py-32 flex flex-col items-center justify-center border border-white/5 rounded-3xl glass-panel relative overflow-hidden"> <div className="w-full py-32 flex flex-col items-center justify-center border border-white/5 rounded-3xl glass-panel relative overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-b from-cyan-500/5 to-transparent"></div> <div className="absolute inset-0 bg-gradient-to-b from-cyan-500/5 to-transparent"></div>
<div className="p-6 rounded-full bg-slate-800/80 mb-6 border border-white/10 shadow-[0_0_40px_rgba(6,182,212,0.15)] relative z-10"> <div className="p-6 rounded-full bg-slate-800/80 mb-6 border border-white/10 shadow-[0_0_40px_rgba(6,182,212,0.15)] relative z-10">
@ -107,18 +107,27 @@ export default function Home() {
</div> </div>
) : ( ) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-6">
{posts.map((post) => ( {posts.map((post) => {
const PROJECT_ID = process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID || '69b84df5003bd8957c5c';
const ENDPOINT = process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT || 'https://appwrite.almeira.com.co/v1';
let imagenUrl = null;
if (post.imagen_destacada_id) {
imagenUrl = `${ENDPOINT}/storage/buckets/${post.imagen_destacada_id.bucket_id}/files/${post.imagen_destacada_id.file_id}/preview?project=${PROJECT_ID}&width=800&height=500&quality=85`;
}
return (
<NewsCard <NewsCard
key={post.$id} key={post.$id}
id={post.$id} id={post.$id}
slug={post.slug || post.$id} slug={post.slug || post.$id}
titulo={post.titulo} titulo={post.titulo}
resumen={post.resumen_redes || post.contenido?.substring(0, 100) + '...'} resumen={post.resumen_redes || post.contenido?.substring(0, 100).replace(/<[^>]*>?/gm, '') + '...'}
tipo={post.tipo} tipo="Noticia"
fecha={post.fecha_publicacion || post.$createdAt} fecha={post.fecha_publicacion || post.$createdAt}
imagenUrl={post.imagen_destacada_id || post.og_image_url} imagenUrl={imagenUrl}
/> />
))} )
})}
</div> </div>
)} )}
</div> </div>