CMS
TinaCMS
Edicion visual en tiempo real
Colecciones
3
posts, pages, authors
Componentes MDX
4
Callout, RecipeCard, Nutrientes, CTA
Deploy
Vercel
Tina Cloud ยท rama main
Flujo editorial
1
Editar en /admin
CM abre nutriVerde.com/admin, edita en interfaz visual
โ
2
Preview live
useTina refleja los cambios en tiempo real al lado derecho
โ
3
Guardar = Git commit
Tina Cloud hace commit del .mdx al repo en GitHub
โ
4
Deploy automatico
Vercel detecta el push y redeploya en ~30s
โ
5
Publicado
Contenido live en nutriverde.com con historial Git
Estructura del proyecto
๐ tina/ (gestionado por TinaCMS)
๐ config.ts
๐ __generated__/ (auto-generado)
๐ client.ts
๐ types.ts
๐ content/
๐ posts/ia-nutricion-personalizada.mdx
๐ posts/receta-bowl-proteico.mdx
๐ posts/bienestar-microbioma.mdx
๐ ... 9 posts mas
๐ pages/home.mdx
๐ pages/sobre-nosotros.mdx
๐ app/
๐ posts/[slug]/page.tsx
๐ blog/page.tsx
๐ src/lib/
๐ content.ts
๐ .env.local (secrets Tina Cloud)
Esquemas de contenido โ tina/config.ts
๐ Blog Posts
content/posts/*.mdx
string
title REQUERIDO
Titulo del articulo (isTitle)
string
description
Resumen del post (textarea)
datetime
publishedAt REQUERIDO
Fecha de publicacion
image
heroImage
Imagen destacada del post
string
category
recetas ยท nutricion ยท bienestar ยท ia-alimentacion
object
author
name ยท avatar ยท role (objeto anidado)
rich-text
body (isBody)
Contenido MDX con templates: Callout, RecipeCard, Nutrientes
๐ Landing Pages
content/pages/*.mdx
string
title REQUERIDO
Titulo de la pagina
object
seo
metaTitle ยท metaDescription ยท ogImage
list
blocks (Page Builder)
Bloques arrastrables para la pagina
Templates de bloques
๐ฆธ
Hero Section
heading ยท subheading ยท bgImage ยท cta
โจ
Features Grid
heading ยท items[]{title, desc, icon}
๐ฃ
CTA Banner
text ยท buttonText ยท buttonLink
โญ
Testimonials
items[]{quote, author, rating}
Configuracion โ tina/config.ts
tina/config.ts
TypeScript
// tina/config.ts โ Configuracion TinaCMS para NutriVerde import { defineConfig } from "tinacms"; export default defineConfig({ branch: process.env.NEXT_PUBLIC_TINA_BRANCH || "main", clientId: process.env.NEXT_PUBLIC_TINA_CLIENT_ID!, token: process.env.TINA_TOKEN!, build: { outputFolder: "admin", // Panel en nutriverde.com/admin publicFolder: "public", }, media: { tina: { mediaRoot: "uploads", // Imagenes en /public/uploads publicFolder: "public", }, }, schema: { collections: [ // ============================== // COLECCION: Blog Posts (MDX) // ============================== { name: "post", label: "Articulos del Blog", path: "content/posts", format: "mdx", fields: [ { type: "string", name: "title", label: "Titulo", isTitle: true, required: true }, { type: "string", name: "description", label: "Resumen", ui: { component: "textarea" } }, { type: "datetime", name: "publishedAt", label: "Fecha publicacion", required: true }, { type: "image", name: "heroImage", label: "Imagen destacada" }, { type: "string", name: "category", label: "Categoria", options: ["recetas", "nutricion", "bienestar", "ia-alimentacion"], }, { type: "object", name: "author", label: "Autor", fields: [ { type: "string", name: "name", label: "Nombre", required: true }, { type: "image", name: "avatar", label: "Avatar" }, { type: "string", name: "role", label: "Rol" }, ], }, { type: "rich-text", name: "body", label: "Contenido", isBody: true, templates: [ { name: "Callout", label: "Caja de Aviso", fields: [ { type: "string", name: "type", label: "Tipo", options: ["info", "warning", "tip", "danger"] }, { type: "rich-text", name: "children", label: "Contenido" }, ], }, { name: "RecipeCard", label: "Tarjeta de Receta", fields: [ { type: "string", name: "name", label: "Nombre receta" }, { type: "number", name: "kcal", label: "Calorias (kcal)" }, { type: "string", name: "time", label: "Tiempo preparacion" }, { type: "string", name: "tags", label: "Etiquetas", list: true }, ], }, ], }, ], }, ], }, });
Edicion visual โ app/posts/[slug]/page.tsx
app/posts/[slug]/page.tsx
TSX
// Server component: obtiene datos en build/request time export default async function PostPage({ params }: { params: { slug: string } }) { const { data, query, variables } = await client.queries.post({ relativePath: `${params.slug}.mdx`, }); return <PostClient data={data} query={query} variables={variables} />; } // Client component: habilita edicion visual con useTina "use client"; function PostClient({ data, query, variables }: PostClientProps) { // useTina: conecta con el panel /admin y refleja cambios en tiempo real const { data: tinaData } = useTina({ query, variables, data }); const post = tinaData.post; return ( <article className="nutriverde-post"> {post.heroImage && <img src={post.heroImage} alt={post.title} />} <h1>{post.title}</h1> <p className="description">{post.description}</p> <div className="author-row"> {post.author?.avatar && <img src={post.author.avatar} alt="" />} <span>{post.author?.name}</span> <time>{new Date(post.publishedAt).toLocaleDateString('es-ES')}</time> </div> {/* TinaMarkdown renderiza MDX con componentes custom */} <TinaMarkdown content={post.body} components={components} /> </article> ); } // Componentes MDX custom para NutriVerde const components = { Callout: ({ type, children }: any) => ( <div className={`callout callout-${type}`}> {type === "tip" && "๐ก"} {type === "warning" && "โ ๏ธ"} <TinaMarkdown content={children} /> </div> ), RecipeCard: ({ name, kcal, time, tags }: any) => ( <div className="recipe-card"> <h3>{name}</h3> <p>{kcal} kcal ยท {time}</p> {tags?.map((t: string) => <span key={t} className="tag">{t}</span>)} </div> ), };
Componentes MDX โ preview
๐งฉ Componentes custom disponibles en el editor visual
Consejo nutricional: Consume proteina en los primeros 30 minutos tras el ejercicio para optimizar la sintesis muscular. El suero de leche y el huevo son fuentes de rapida absorcion.
Atencion: Las personas con hipotiroidismo deben consultar al medico antes de seguir una dieta cetogenica estricta.
NutriVerde IA analiza tu microbioma y ajusta las recomendaciones semanalmente. Conecta tu wearable para mayor precision.
๐ฅ
Bowl Proteico Verde
Queries tipadas โ src/lib/content.ts
src/lib/content.ts
TypeScript
// Obtener todos los posts async function getAllPosts() { const res = await client.queries .postConnection({ sort: "publishedAt", last: 100 }); return res.data.postConnection.edges ?.map((edge) => ({ slug: edge?.node?._sys.filename, title: edge?.node?.title, cat: edge?.node?.category, author: edge?.node?.author, })) ?? []; } // Filtrar por categoria async function getPostsByCategory(cat: string) { const res = await client.queries .postConnection({ filter: { category: { eq: cat } }, }); return res.data.postConnection.edges ?.map((e) => e?.node) ?? []; } // Post individual async function getPost(slug: string) { const res = await client.queries .post({ relativePath: `${slug}.mdx` }); return res.data.post; }
Instalacion y arranque
๐ฅ๏ธ Terminal โ desde la raiz del proyecto nutriverde-web/
$
npx @tinacms/cli@latest init
Instala TinaCMS y genera tina/config.ts inicial
$
npm install tinacms @tinacms/cli
Dependencias principales
$
npx tinacms dev -c "next dev"
Dev server con edicion visual en localhost:3000/admin
$
npx tinacms build && next build
Build de produccion para Vercel
๐ .env.local โ Variables de entorno (Tina Cloud)
NEXT_PUBLIC_TINA_CLIENT_ID
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
TINA_TOKEN
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
NEXT_PUBLIC_TINA_BRANCH
main
Buenas practicas para NutriVerde
1
Git es tu base de datos
Cada edicion del CM crea un commit en GitHub. El historial de articulos es el historial de Git. Rollback = git revert.
2
MDX para contenido rico
Los componentes Callout y RecipeCard estan disponibles directamente en el editor WYSIWYG. El CM los inserta con un clic.
3
Queries tipadas, nunca raw GraphQL
Usa el client auto-generado en tina/__generated__. TypeScript te avisa si el esquema cambia y rompe algun query.
4
Workflow de ramas para reviews
El CM puede crear articulos en rama draft. Los devs hacen review del MDX como cualquier PR antes de mergear a main.
5
Page builder sin codigo
Los bloques hero, features y CTA son arrastrables. El CM monta nuevas landing pages de NutriVerde sin tocar TSX.
6
Git LFS para imagenes pesadas
Las fotos de recetas e ingredientes pueden ser grandes. Configura Git LFS para mantener el repo liviano en GitHub.