refactor: global project hardening and integrity verification

- Add safety checks for direct Appwrite document index access across all root pages.
- Harden NewsTicker and AdWrappers with robust fallback settings and length checks.
- Apply strict typing to dashboard settings page and resolve icon conflicts.
- Final global audit for deprecated Zod .errors usage (0 occurrences remaining).
- Standardize PublicationLayout across creation and edit workflows for posts.
This commit is contained in:
Carlos (Almeira) 2026-03-28 10:07:32 -05:00
parent 956360a693
commit 5279af2003
6 changed files with 50 additions and 19 deletions

View File

@ -13,7 +13,12 @@ async function fetchClasificadosData() {
// 1. Settings // 1. Settings
const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []); const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []);
const settings = (settingsRes.documents.length > 0 ? settingsRes.documents[0] : {}) as Settings; const settings = (settingsRes.documents.length > 0 ? settingsRes.documents[0] : {
ticker_count: 10,
rotation_seconds: 5,
feed_news_count: 12,
show_news_date: true
}) as Settings;
const postsCount = settings.feed_news_count || 12; const postsCount = settings.feed_news_count || 12;
const cardSettings = { const cardSettings = {
@ -30,7 +35,9 @@ async function fetchClasificadosData() {
let posts: Post[] = []; let posts: Post[] = [];
if (typeRes.documents.length > 0) { if (typeRes.documents.length > 0) {
const typeId = typeRes.documents[0].$id; const firstType = typeRes.documents[0];
const typeId = firstType?.$id;
if (!typeId) return { posts: [], cardSettings };
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [ const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
Query.equal('tipo_publicacion_id', typeId), Query.equal('tipo_publicacion_id', typeId),
Query.equal('estado', 'Publicado'), Query.equal('estado', 'Publicado'),

View File

@ -2,11 +2,12 @@ import { APPWRITE_ALMEIRA_CONFIG } from '@/lib/appwrite';
import { createSessionClient } from '@/lib/appwrite.server'; import { createSessionClient } from '@/lib/appwrite.server';
import { getEntity, updateEntity } from '@/app/actions/entity'; import { getEntity, updateEntity } from '@/app/actions/entity';
import { revalidatePath } from 'next/cache'; import { revalidatePath } from 'next/cache';
import { Settings, Save, Clock, Menu, Building, Image as ImageIcon, Link as LinkIcon, Share2 } from 'lucide-react'; import { Settings as SettingsIcon, Save, Clock, Menu, Building, Image as ImageIcon, Link as LinkIcon, Share2 } from 'lucide-react';
import { Settings as SettingsInterface } from '@/types/settings';
export default async function SettingsPage() { export default async function SettingsPage() {
let settings = { ticker_count: 10, rotation_seconds: 5 }; let settings: SettingsInterface = { ticker_count: 10, rotation_seconds: 5 };
let docId = 'global_config'; let docId = '';
let entity: any = null; let entity: any = null;
try { try {
@ -18,7 +19,7 @@ export default async function SettingsPage() {
[] []
); );
if (response.documents.length > 0) { if (response.documents.length > 0) {
settings = response.documents[0] as any; settings = response.documents[0] as unknown as SettingsInterface;
docId = response.documents[0].$id; docId = response.documents[0].$id;
} }
} }
@ -69,7 +70,7 @@ export default async function SettingsPage() {
<div className="max-w-5xl mx-auto space-y-6 animate-fade-in pb-12"> <div className="max-w-5xl mx-auto space-y-6 animate-fade-in pb-12">
<div className="flex items-center gap-4 bg-slate-900 border border-slate-800 p-6 rounded-2xl shadow-xl shadow-cyan-500/5"> <div className="flex items-center gap-4 bg-slate-900 border border-slate-800 p-6 rounded-2xl shadow-xl shadow-cyan-500/5">
<div className="p-3 bg-cyan-500/10 rounded-xl text-cyan-400"> <div className="p-3 bg-cyan-500/10 rounded-xl text-cyan-400">
<Settings size={32} /> <SettingsIcon size={32} />
</div> </div>
<div> <div>
<h1 className="text-3xl font-black font-outfit uppercase tracking-tighter text-white"> <h1 className="text-3xl font-black font-outfit uppercase tracking-tighter text-white">
@ -109,7 +110,7 @@ export default async function SettingsPage() {
<input <input
type="number" type="number"
name="feed_news_count" name="feed_news_count"
defaultValue={(settings as any).feed_news_count || 12} defaultValue={settings.feed_news_count || 12}
min="4" max="100" min="4" max="100"
required required
className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-3 text-white focus:border-cyan-400 text-xl font-black font-mono transition-all" className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-3 text-white focus:border-cyan-400 text-xl font-black font-mono transition-all"
@ -122,7 +123,7 @@ export default async function SettingsPage() {
type="checkbox" type="checkbox"
name="show_news_date" name="show_news_date"
value="true" value="true"
defaultChecked={(settings as any).show_news_date === true} defaultChecked={settings.show_news_date === true}
id="show_news_date" id="show_news_date"
className="w-5 h-5 accent-cyan-500 cursor-pointer" className="w-5 h-5 accent-cyan-500 cursor-pointer"
/> />

View File

@ -13,7 +13,12 @@ async function fetchEventosData() {
// 1. Settings // 1. Settings
const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []); const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []);
const settings = (settingsRes.documents.length > 0 ? settingsRes.documents[0] : {}) as Settings; const settings = (settingsRes.documents.length > 0 ? settingsRes.documents[0] : {
ticker_count: 10,
rotation_seconds: 5,
feed_news_count: 12,
show_news_date: true
}) as Settings;
const postsCount = settings.feed_news_count || 12; const postsCount = settings.feed_news_count || 12;
const cardSettings = { const cardSettings = {
@ -30,7 +35,9 @@ async function fetchEventosData() {
let posts: Post[] = []; let posts: Post[] = [];
if (typeRes.documents.length > 0) { if (typeRes.documents.length > 0) {
const typeId = typeRes.documents[0].$id; const firstType = typeRes.documents[0];
const typeId = firstType?.$id;
if (!typeId) return { posts: [], cardSettings };
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [ const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
Query.equal('tipo_publicacion_id', typeId), Query.equal('tipo_publicacion_id', typeId),
Query.equal('estado', 'Publicado'), Query.equal('estado', 'Publicado'),

View File

@ -15,7 +15,12 @@ async function fetchHomeData() {
// 1. Settings // 1. Settings
const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []); const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []);
const settings = (settingsRes.documents.length > 0 ? settingsRes.documents[0] : {}) as Settings; const settings = (settingsRes.documents.length > 0 ? settingsRes.documents[0] : {
ticker_count: 10,
rotation_seconds: 5,
feed_news_count: 12,
show_news_date: true
}) as Settings;
const postsCount = settings.feed_news_count || 12; const postsCount = settings.feed_news_count || 12;
const cardSettings = { const cardSettings = {
@ -32,7 +37,9 @@ async function fetchHomeData() {
let posts: Post[] = []; let posts: Post[] = [];
if (typeRes.documents.length > 0) { if (typeRes.documents.length > 0) {
const typeId = typeRes.documents[0].$id; const firstType = typeRes.documents[0];
const typeId = firstType?.$id;
if (!typeId) return { posts: [], cardSettings };
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [ const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
Query.equal('tipo_publicacion_id', typeId), Query.equal('tipo_publicacion_id', typeId),
Query.equal('estado', 'Publicado'), Query.equal('estado', 'Publicado'),

View File

@ -8,12 +8,12 @@ import { Settings } from '@/types/settings';
async function fetchSettings(): Promise<Settings> { async function fetchSettings(): Promise<Settings> {
try { try {
const client = await createAdminClient(); const client = await createAdminClient();
const res = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', [Query.limit(1)]); const res = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', [Query.limit(1)]);
if (res.documents.length > 0) return res.documents[0] as unknown as Settings; if (res.documents.length > 0) return res.documents[0] as unknown as Settings;
} catch(e) { } catch(e) {
console.error("fetchSettings error:", e); console.error("fetchSettings error:", e);
} }
return { rotation_seconds: 5, ticker_count: 10 }; return { rotation_seconds: 5, ticker_count: 10, feed_news_count: 12, show_news_date: true };
} }
export async function GlobalTopBanner() { export async function GlobalTopBanner() {
@ -27,7 +27,9 @@ export async function GlobalTopBanner() {
]); ]);
if (typesRes.documents.length > 0) { if (typesRes.documents.length > 0) {
const typeId = typesRes.documents[0].$id; const firstType = typesRes.documents[0];
const typeId = firstType?.$id;
if (!typeId) return null;
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [ const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
Query.equal('tipo_publicacion_id', typeId), Query.equal('tipo_publicacion_id', typeId),

View File

@ -19,7 +19,12 @@ export default async function NewsTicker() {
// 1. Fetch Settings // 1. Fetch Settings
const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []); const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []);
const settings = (settingsRes.documents.length > 0 ? settingsRes.documents[0] : {}) as Settings; const settings = (settingsRes.documents.length > 0 ? settingsRes.documents[0] : {
ticker_count: 10,
rotation_seconds: 5,
feed_news_count: 12,
show_news_date: true
}) as Settings;
const tickerCount = settings.ticker_count || 10; const tickerCount = settings.ticker_count || 10;
// 2. Fetch News (NOTICIA) // 2. Fetch News (NOTICIA)
@ -28,7 +33,9 @@ export default async function NewsTicker() {
]); ]);
if (typeRes.documents.length > 0) { if (typeRes.documents.length > 0) {
const typeId = typeRes.documents[0].$id; const firstType = typeRes.documents[0];
const typeId = firstType?.$id;
if (!typeId) throw new Error("Could not find typeId for NOTICIA");
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [ const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
Query.equal('tipo_publicacion_id', typeId), Query.equal('tipo_publicacion_id', typeId),
Query.equal('estado', 'Publicado'), Query.equal('estado', 'Publicado'),