🧱 Crear Custom Post Type + Meta
phpCopyEdit// functions.php o plugin
add_action('init', function() {
register_post_type('curso', [
'label' => 'Cursos',
'public' => true,
'show_in_rest' => true,
'supports' => ['title', 'editor', 'custom-fields'],
]);
});
register_post_meta('curso', 'duracion', [
'type' => 'string',
'show_in_rest' => true,
'single' => true,
'sanitize_callback' => 'sanitize_text_field',
]);
🧱 Crear Bloque Gutenberg Personalizado (JSX)
jsxCopyEdit// edit.js
import { useBlockProps } from '@wordpress/block-editor';
export default function Edit({ attributes, setAttributes }) {
return (
<div {...useBlockProps()}>
<h3>Mi bloque personalizado</h3>
<p>Contenido editable o dinámico aquÃ</p>
</div>
);
}
// save.js
export default function Save() {
return <div><h3>Mi bloque guardado</h3></div>;
}
jsonCopyEdit// block.json
{
"apiVersion": 2,
"name": "mi-bloque/personalizado",
"title": "Mi Bloque",
"category": "widgets",
"editorScript": "file:./index.js"
}
🧠WP-CLI para agregar una columna a la BD
phpCopyEdit// archivo update-db.php
global $wpdb;
$table = $wpdb->prefix . 'posts';
if ( $wpdb->get_var("SHOW COLUMNS FROM $table LIKE 'ltv'") !== 'ltv' ) {
$wpdb->query("ALTER TABLE $table ADD COLUMN ltv DECIMAL(10,2) DEFAULT 0;");
}
bashCopyEditwp eval-file update-db.php
🔸 React + Next.js
📥 Fetch desde WordPress REST API
jsCopyEditimport { useEffect, useState } from 'react';
export default function Cursos() {
const [data, setData] = useState([]);
useEffect(() => {
fetch('https://tusitio.com/wp-json/wp/v2/curso')
.then(res => res.json())
.then(setData);
}, []);
return (
<div>
{data.map(curso => (
<article key={curso.id}>
<h3>{curso.title.rendered}</h3>
</article>
))}
</div>
);
}
🧊 Lazy Load de componentes
jsCopyEditimport dynamic from 'next/dynamic';
const ComponentePesado = dynamic(() => import('./ComponentePesado'), {
ssr: false,
loading: () => <p>Cargando…</p>,
});
export default function Page() {
return (
<>
<h1>Mi Página</h1>
<ComponentePesado />
</>
);
}
👀 Lazy Load con IntersectionObserver
jsCopyEditimport { useEffect, useRef, useState } from 'react';
export default function LazySection() {
const ref = useRef();
const [visible, setVisible] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) setVisible(true);
});
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, []);
return <div ref={ref}>{visible && <p>Contenido cargado perezosamente</p>}</div>;
}
âš¡ Redux Slice Minimal
jsCopyEdit// store.js
import { configureStore, createSlice } from '@reduxjs/toolkit';
const userSlice = createSlice({
name: 'user',
initialState: { nombre: '' },
reducers: {
setNombre: (state, action) => {
state.nombre = action.payload;
},
},
});
export const { setNombre } = userSlice.actions;
export default configureStore({
reducer: {
user: userSlice.reducer,
},
});
jsCopyEdit// uso en componente
import { useSelector, useDispatch } from 'react-redux';
import { setNombre } from './store';
const Componente = () => {
const nombre = useSelector((state) => state.user.nombre);
const dispatch = useDispatch();
return (
<div>
<input onChange={e => dispatch(setNombre(e.target.value))} />
<p>{nombre}</p>
</div>
);
};
🧠Debounce con React
jsCopyEditimport { useState, useEffect } from 'react';
function useDebouncedValue(value, delay = 500) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const handler = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(handler);
}, [value]);
return debounced;
}
🚀 Core Web Vitals – Mejores prácticas
- ✅ LCP: preload imágenes importantes + AVIF, critical CSS inline.
- ✅ CLS: reservar espacio con width/height fijo,
font-display: swap.
- ✅ TBT: dividir bundles, usar
requestIdleCallback, evitar JS innecesario.
- ✅ Caching: usar Redis para guardar respuestas de API y
wp_cache_set.
🧪 Test Básico de Componente React
jsCopyEditimport { render, screen } from '@testing-library/react';
import Componente from './Componente';
test('renderiza tÃtulo', () => {
render(<Componente />);
expect(screen.getByText(/mi componente/i)).toBeInTheDocument();
});