fix(serialization): resolve typescript types in map functions preventing successful production build, and add global error boundary failover

This commit is contained in:
Carlos (Almeira) 2026-07-18 08:43:39 -05:00
parent 6ed5cb69e6
commit 84d48ad096
7 changed files with 93 additions and 11 deletions

View File

@ -132,7 +132,7 @@ export default async function ClasificadosPage() {
</div>
) : (
<div className={`grid grid-cols-1 md:grid-cols-2 ${hasSidebar ? 'lg:grid-cols-2 xl:grid-cols-3' : 'lg:grid-cols-3 xl:grid-cols-4'} gap-6`}>
{posts.map((post) => {
{posts.map((post: 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 = post.og_image_url || null;

32
src/app/error.tsx Normal file
View File

@ -0,0 +1,32 @@
'use client'
import { useEffect } from 'react'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
// Solo redirigir si no estamos ya en el dominio de respaldo para evitar bucles infinitos
if (typeof window !== 'undefined' && window.location.hostname !== 'news.almeira.co') {
window.location.href = 'https://news.almeira.co';
}
}, [error]);
return (
<div className="min-h-screen flex flex-col items-center justify-center bg-slate-950 text-white p-4 font-sans">
<h2 className="text-3xl font-black mb-4 uppercase tracking-widest text-cyan-500">SEÑAL INTERRUMPIDA</h2>
<p className="text-slate-400 mb-8 max-w-md text-center text-lg">
Hemos detectado una anomalía en este servidor. Redirigiendo automáticamente a nuestro portal de respaldo...
</p>
<div className="w-10 h-10 border-4 border-cyan-500 border-t-transparent rounded-full animate-spin"></div>
<a href="https://news.almeira.co" className="mt-8 px-6 py-2 border border-slate-700 rounded-full text-sm text-slate-400 hover:text-white hover:border-slate-500 transition-colors">
Ir manualmente
</a>
</div>
)
}

View File

@ -132,7 +132,7 @@ export default async function EventosPage() {
</div>
) : (
<div className={`grid grid-cols-1 md:grid-cols-2 ${hasSidebar ? 'lg:grid-cols-2 xl:grid-cols-3' : 'lg:grid-cols-3 xl:grid-cols-4'} gap-6`}>
{posts.map((post) => {
{posts.map((post: 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 = post.og_image_url || null;

45
src/app/global-error.tsx Normal file
View File

@ -0,0 +1,45 @@
'use client'
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<html lang="es">
<head>
<script dangerouslySetInnerHTML={{
__html: `
if (window.location.hostname !== 'news.almeira.co') {
window.location.href = 'https://news.almeira.co';
}
`
}} />
</head>
<body style={{ backgroundColor: '#020617', color: 'white', fontFamily: 'sans-serif', margin: 0, padding: 0 }}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', padding: '20px', textAlign: 'center' }}>
<h2 style={{ fontSize: '2rem', fontWeight: 900, marginBottom: '1rem', color: '#06b6d4', textTransform: 'uppercase', letterSpacing: '0.1em' }}>SEÑAL CRÍTICA INTERRUMPIDA</h2>
<p style={{ color: '#94a3b8', marginBottom: '2rem', maxWidth: '400px', fontSize: '1.125rem' }}>
Fallo mayor en la transmisión principal. Reencaminando el tráfico a nuestro servidor secundario...
</p>
<div style={{ width: '40px', height: '40px', border: '4px solid #06b6d4', borderTopColor: 'transparent', borderRadius: '50%', animation: 'spin 1s linear infinite' }}></div>
<a href="https://news.almeira.co" style={{ marginTop: '2rem', padding: '10px 20px', border: '1px solid #334155', borderRadius: '9999px', color: '#94a3b8', textDecoration: 'none', fontSize: '0.875rem' }}>
Ir a news.almeira.co
</a>
<style dangerouslySetInnerHTML={{
__html: `
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`
}} />
</div>
</body>
</html>
)
}

View File

@ -212,7 +212,7 @@ export default async function Home(props: { searchParams: Promise<{ [key: string
</div>
) : (
<div className={`grid grid-cols-1 md:grid-cols-2 ${hasSidebar ? 'lg:grid-cols-2 xl:grid-cols-3' : 'lg:grid-cols-3 xl:grid-cols-4'} gap-6`}>
{posts.map((post) => {
{posts.map((post: 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 = post.og_image_url || null;

View File

@ -34,7 +34,10 @@ export const NewsCard = ({
imageAspect = 'aspect-[16/10]'
}: NewsCardProps) => {
const titulo = decodeHtmlEntities(rawTitulo);
const resumen = decodeHtmlEntities(rawResumen);
// Strip HTML tags safely for a plain text excerpt
const plainTextResumen = (rawResumen || '').replace(/<[^>]*>?/gm, ' ').replace(/\s+/g, ' ').trim();
const resumen = decodeHtmlEntities(plainTextResumen);
// Formatear fecha
const fechaObj = new Date(fecha);
@ -62,9 +65,14 @@ export const NewsCard = ({
{/* Imagen Destacada */}
<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>
<div className="absolute inset-0 bg-gradient-to-t from-slate-950 via-slate-950/20 to-transparent z-20 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" />
<>
{/* Blurred Background to fill empty spaces nicely */}
<img src={imagenUrl} alt="" className="absolute inset-0 w-full h-full object-cover blur-2xl opacity-40 scale-110" aria-hidden="true" />
{/* Main Image contained properly without crop */}
<img src={imagenUrl} alt={titulo} className="relative z-10 w-full h-full object-contain transition-transform duration-700 group-hover:scale-105" />
</>
) : (
<div className="w-full h-full flex items-center justify-center bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-slate-800 to-slate-900">
{/* No placeholder text as per user request */}
@ -73,14 +81,12 @@ export const NewsCard = ({
{/* Badge/Tag - Habilitable por prop */}
{showCategory && (
<div className="absolute top-4 left-4 z-20">
<div className="absolute top-4 left-4 z-30">
<span className="px-3 py-1.5 rounded-full text-xs font-bold uppercase tracking-wider backdrop-blur-md border border-white/20 bg-black/40 text-cyan-300 shadow-[0_0_10px_rgba(6,182,212,0.2)]">
{tipo?.toUpperCase() || 'NOTICIA'}
</span>
</div>
)}
{/* No button here */}
</div>
{/* Contenido */}

View File

@ -71,8 +71,7 @@ export async function createAdminClient() {
patchClientInstance(client);
const keyPrefix = process.env.APPWRITE_API_KEY ? process.env.APPWRITE_API_KEY.trim().substring(0, 15) + '...' : 'EMPTY_KEY';
console.log('Admin Client initialized with key:', keyPrefix, 'Length:', process.env.APPWRITE_API_KEY?.length);
return {
get account() {