DIDAC SORIA / WORDPRESS & CUSTOM CODE
Código WordPress Diseñado para Resolver, Integrar y Automatizar
Escribo Código WordPress a Medida cuando una configuración estándar no resuelve correctamente un problema editorial, técnico o de negocio.
Este Portfolio de Desarrollo WordPress reúne Plugins reales, Arquitectura Multilingüe, interfaces adaptables, migraciones reversibles y Automatización con WP-CLI. Cada caso muestra qué debía funcionar, cómo se estructuró y qué parte del código demuestra la solución.

01 / RESUMEN
Código Creado para Restricciones Reales
Mis ejemplos de Plugins WordPress no son ejercicios aislados. DS Portfolio reorganiza una biblioteca visual sin tocar los contenidos originales. WP CVDS convierte siete módulos heredados en un Plugin mantenible y protege documentos privados. El sistema de CPT de didacsoria.com mantiene URLs limpias y traducciones relacionadas. Las herramientas WP-CLI hacen que cambios masivos se puedan revisar, respaldar y verificar.
Proyectos propios: didacsoria.com · StudioDDC · cv.didacsoria.com
WordPress · PHP · JavaScript · HTML5 · CSS3 · Gutenberg · Polylang · WP-CLI · Genesis · Yoast SEO
8
Plantillas CV verificadas
7
Módulos heredados consolidados
5
Shortcodes en DS Portfolio
3
Idiomas en WP CVDS
| Lo que ve la Persona | Lo que Resuelve el Sistema | Prueba en esta Página |
|---|---|---|
| Filtros y búsqueda claros | Registros bilingües, Taxonomías y renderizado accesible | DS Portfolio y carga condicional |
| Currículum usable en móvil | Navegación adaptable y documentos privados | WP CVDS y token firmado |
| URLs coherentes por idioma | CPT públicos, Polylang y reglas exactas | Arquitectura de rutas |
| Cambios consistentes | Simulación, copia, aplicación y verificación | Automatización con WP-CLI |

02 / PLUGIN DESTACADO
DS Portfolio 1.3.3
StudioDDC necesitaba mostrar Diseño Web, Diseño Gráfico y Temas WordPress con filtros útiles, búsqueda, ordenación, lightbox y comportamiento táctil. El reto era migrar sin modificar Posts, traducciones de Polylang, Taxonomías, Pretty Links ni metadatos de imágenes.
Modelo
Un registro privado conserva campos en inglés y español y referencia los adjuntos existentes.
Interfaz
Filtros accesibles, búsqueda opcional, Grid o Masonry y respuesta específica para dispositivos táctiles.
Seguridad del Cambio
Auditoría previa, migración reversible, estado incremental, verificación y rollback.
IMPLEMENTACIÓN / PHP
Carga Condicional de Assets Según los Shortcodes Utilizados
El Plugin no añade CSS ni JavaScript globalmente. Primero comprueba el contexto, obtiene el contenido actual y solo carga sus recursos cuando uno de los Shortcodes registrados está realmente presente.
public static function maybe_enqueue_assets() {
// ENQUEUE PORTFOLIO ASSETS ONLY WHEN THE CURRENT CONTENT REQUIRES THEM.
if ( ! is_singular() ) {
return;
}
$post = get_post();
if ( ! $post instanceof WP_Post ) {
return;
}
if ( '' === trim( (string) $post->post_content ) ) {
return;
}
$shortcodes = array_unique(
array_merge(
array_keys( self::$services_shortcodes ),
array_keys( self::$shortcodes )
)
);
foreach ( $shortcodes as $shortcode ) {
if ( ! is_string( $shortcode ) || '' === $shortcode ) {
continue;
}
if ( has_shortcode( $post->post_content, $shortcode ) ) {
self::enqueue_assets();
return;
}
}
}
private static function enqueue_assets() {
// REGISTER AND ENQUEUE THE FRONTEND ASSETS FOR THE PORTFOLIO INTERFACE.
wp_enqueue_style(
'ds-portfolio',
plugins_url( 'assets/css/ds-portfolio.css', DS_PORTFOLIO_FILE ),
array(),
DS_PORTFOLIO_VERSION
);
wp_enqueue_script(
'ds-portfolio',
plugins_url( 'assets/js/ds-portfolio.js', DS_PORTFOLIO_FILE ),
array(),
DS_PORTFOLIO_VERSION,
true
);
wp_script_add_data(
'ds-portfolio',
'strategy',
'defer'
);
}
Patrón empleado para evitar recursos globales innecesarios en interfaces basadas en Shortcodes.
03 / WP CVDS
Un Plugin para una Experiencia Profesional Completa
WP CVDS 1.2.0 consolida siete módulos originales en un único Plugin para cv.didacsoria.com. Conserva navegación EN, ES y CA, desplazamiento, interfaz adaptable, ocho plantillas de Widget verificadas, Branding de acceso y descargas privadas.
La protección limita el acceso a 5 intentos durante 15 minutos, verifica el PIN mediante hash, firma un token con las salts de WordPress y entrega los PDF desde una carpeta privada fuera de Uploads.

IMPLEMENTACIÓN / SEGURIDAD PHP
Creación y Verificación de Tokens Firmados con Caducidad
La autorización no necesita guardar el PIN en una Cookie. El sistema firma una fecha de expiración con HMAC, utiliza las salts de WordPress y compara las firmas mediante hash_equals().
function cvds_create_access_token( $expires ) {
// CREATE A SIGNED ACCESS TOKEN WITH A FIXED EXPIRATION.
$expires = absint( $expires );
if ( $expires <= time() ) {
return '';
}
$payload = 'cvds|' . $expires;
$signature = hash_hmac(
'sha256',
$payload,
wp_salt( 'auth' )
);
return $expires . '.' . $signature;
}
function cvds_verify_access_token( $token ) {
// VERIFY THE SIGNATURE AND EXPIRATION OF AN ACCESS TOKEN.
if ( ! is_string( $token ) || '' === $token ) {
return false;
}
$parts = explode( '.', $token, 2 );
if ( 2 !== count( $parts ) ) {
return false;
}
$expires = absint( $parts[0] );
$signature = sanitize_text_field( $parts[1] );
if (
0 === $expires ||
$expires <= time() ||
64 !== strlen( $signature )
) {
return false;
}
$payload = 'cvds|' . $expires;
$expected = hash_hmac(
'sha256',
$payload,
wp_salt( 'auth' )
);
return hash_equals(
$expected,
$signature
);
}
function cvds_has_valid_access_cookie() {
// VALIDATE THE ACCESS COOKIE WITHOUT TRUSTING RAW REQUEST DATA.
if ( empty( $_COOKIE['cvds_access'] ) ) {
return false;
}
$token = sanitize_text_field(
wp_unslash(
$_COOKIE['cvds_access']
)
);
return cvds_verify_access_token(
$token
);
}
La muestra enseña creación, validación, expiración, sanitización y comparación segura de la firma.

04 / ARQUITECTURA MULTILINGÜE
Contenido Traducible con URLs que Parecen Páginas Normales
En didacsoria.com, ds_service y ds_portfolio son Tipos de Contenido Públicos compatibles con Gutenberg, Revisiones, Imagen Destacada, Extracto, Campos Personalizados y jerarquía. Polylang vincula sus versiones y el sistema genera rutas de raíz sin una base artificial de CPT.
La URL inglesa de este caso es /wordpress-development-portfolio/ y su equivalente español utiliza /es/portfolio-desarrollo-wordpress/. Son contenidos independientes, relacionados como traducciones y preparados para metadatos SEO específicos.
IMPLEMENTACIÓN / WORDPRESS + POLYLANG
Construcción Segura de Rutas Multilingües en la Raíz
La ruta se genera desde el objeto WordPress, elimina segmentos vacíos y añade el idioma únicamente cuando no coincide con el idioma predeterminado de Polylang.
function ds_get_root_content_language( $post_id ) {
// GET THE POLYLANG LANGUAGE SLUG FOR ROOT CONTENT.
$post_id = absint( $post_id );
if (
0 === $post_id ||
! function_exists( 'pll_get_post_language' )
) {
return '';
}
$language = pll_get_post_language(
$post_id,
'slug'
);
return is_string( $language )
? sanitize_key( $language )
: '';
}
function ds_get_root_content_path( $post ) {
// BUILD A ROOT CONTENT PATH WITH THE CORRECT LANGUAGE PREFIX.
if ( ! $post instanceof WP_Post ) {
return '';
}
if (
! in_array(
$post->post_type,
array(
'ds_service',
'ds_portfolio',
),
true
)
) {
return '';
}
$slug = sanitize_title(
$post->post_name
);
if ( '' === $slug ) {
return '';
}
$parts = array(
$slug,
);
$language = ds_get_root_content_language(
$post->ID
);
$default_language = '';
if ( function_exists( 'pll_default_language' ) ) {
$default_language = pll_default_language(
'slug'
);
$default_language = is_string(
$default_language
)
? sanitize_key( $default_language )
: '';
}
if (
'' !== $language &&
$language !== $default_language
) {
array_unshift(
$parts,
$language
);
}
$parts = array_filter(
array_map(
'sanitize_title',
$parts
)
);
return implode(
'/',
$parts
);
}
function ds_get_root_content_url( $post_id ) {
// BUILD THE PUBLIC URL FOR A ROOT SERVICE OR PORTFOLIO ITEM.
$post = get_post(
absint( $post_id )
);
if ( ! $post instanceof WP_Post ) {
return '';
}
$path = ds_get_root_content_path(
$post
);
if ( '' === $path ) {
return '';
}
return home_url(
user_trailingslashit(
$path
)
);
}
La separación entre idioma, path y URL permite validar cada parte de forma independiente y reutilizar la lógica en redirects, enlaces y comprobaciones WP-CLI.
05 / AUTOMATIZACIÓN
WP-CLI para Cambios que se Pueden Revisar y Repetir
Mis herramientas de Automatización WordPress separan comprobación, simulación y aplicación. Las operaciones destructivas se preparan con Backup, Dry Run, validación de argumentos y Verificación posterior.

| Fase | Qué Comprueba | Valor Práctico |
|---|---|---|
| Check | Plugins, CPT, idiomas, enlaces y archivos | Falla antes de escribir si falta una dependencia |
| Dry Run | Registros encontrados y cambios previstos | Permite revisar el alcance |
| Backup | Valores originales o export de base de datos | Conserva una vía clara de recuperación |
| Apply | Solo operaciones validadas y confirmadas | Evita cambios amplios e imprecisos |
| Verify | Contenido, metadatos, URLs y relaciones | Comprueba el resultado final |
El mismo patrón se utiliza para despliegues de contenido bilingüe, migraciones, metadata, búsquedas y reemplazos controlados.
CÓDIGO COMPLETO / BASH + WP-CLI
Search Replace Seguro con Dry Run Obligatorio
Este script completo de mi WP-CLI Operations Toolkit obliga a ejecutar una simulación antes de modificar la base de datos y requiere una confirmación explícita para aplicar el reemplazo real.
#!/usr/bin/env bash
#
# Safe Search and Replace
#
# Wraps `wp search-replace` in a mandatory dry-run step. The script always
# runs in dry-run mode first and only performs the real replacement when
# --confirm is explicitly passed. It does not create a backup itself; run
# wp-cli-backup-before-destructive.sh first.
#
# Usage:
# ./wp-cli-safe-search-replace.sh --path=/path/to/wordpress \
# --search="http://old.example.com" --replace="https://new.example.com" [--confirm]
set -euo pipefail
WP_PATH=""
SEARCH=""
REPLACE=""
CONFIRM=false
for arg in "$@"; do
case "$arg" in
--path=*)
WP_PATH="${arg#*=}"
;;
--search=*)
SEARCH="${arg#*=}"
;;
--replace=*)
REPLACE="${arg#*=}"
;;
--confirm)
CONFIRM=true
;;
esac
done
if [ -z "$WP_PATH" ] || [ -z "$SEARCH" ] || [ -z "$REPLACE" ]; then
echo "Error: --path, --search, and --replace are required." >&2
exit 1
fi
echo "Running dry-run search-replace: '$SEARCH' -> '$REPLACE'"
wp search-replace "$SEARCH" "$REPLACE" \
--path="$WP_PATH" \
--all-tables-with-prefix \
--dry-run \
--report-changed-only
if [ "$CONFIRM" != true ]; then
echo "----------------------------------------"
echo "Dry run only. No changes were made."
echo "Confirm a backup exists, then re-run with --confirm to apply the replacement."
exit 0
fi
echo "----------------------------------------"
echo "Confirm flag detected. Applying search-replace."
wp search-replace "$SEARCH" "$REPLACE" \
--path="$WP_PATH" \
--all-tables-with-prefix \
--report-changed-only
echo "Search-replace complete. Verify affected pages and flush any caches."
Source: wp-cli-safe-search-replace.sh
06 / ENTREGA FRONTEND
Código Pequeño Cuando una Solución Pequeña es Suficiente
No todo problema necesita un Plugin grande. El Shortcode de Servicios puede trabajar con IDs reales de WordPress para obtener Enlace, Imagen Destacada y Extracto. Si un contenido no existe o no es público, no imprime una tarjeta rota.
Esta es una parte importante de mi enfoque como Desarrollador WordPress: ajustar el tamaño de la solución al problema, mantener el contenido editable y aplicar validación, sanitización y escaping en los límites correctos.
IMPLEMENTACIÓN / WORDPRESS FRONTEND
Renderizado de Tarjetas con Datos Reales de WordPress
El callback valida los IDs, descarta contenidos que no deben mostrarse y escapa URL, imagen, título y extracto en el momento de salida.
function ds_render_service_cards_shortcode( $atts ) {
// RENDER SERVICE CARDS FROM VALID WORDPRESS POST IDS.
$atts = shortcode_atts(
array(
'ids' => '',
),
$atts,
'ds_service_cards'
);
$ids = wp_parse_id_list(
$atts['ids']
);
if ( empty( $ids ) ) {
return '';
}
$items = array();
foreach ( $ids as $post_id ) {
$post = get_post(
$post_id
);
if (
! $post instanceof WP_Post ||
'publish' !== $post->post_status ||
'ds_service' !== $post->post_type
) {
continue;
}
$permalink = get_permalink(
$post_id
);
if ( ! $permalink ) {
continue;
}
$items[] = array(
'title' => get_the_title( $post_id ),
'url' => $permalink,
'image_url' => get_the_post_thumbnail_url(
$post_id,
'large'
),
'excerpt' => wp_strip_all_tags(
get_the_excerpt( $post_id )
),
);
}
if ( empty( $items ) ) {
return '';
}
ob_start();
?>
<div class="ds-service-cards">
<?php foreach ( $items as $item ) : ?>
<article class="ds-service-card">
<a
class="ds-service-card__link"
href="<?php echo esc_url( $item['url'] ); ?>"
>
<?php if ( ! empty( $item['image_url'] ) ) : ?>
<img
class="ds-service-card__image"
src="<?php echo esc_url( $item['image_url'] ); ?>"
alt=""
loading="lazy"
decoding="async"
/>
<?php endif; ?>
<h3 class="ds-service-card__title">
<?php echo esc_html( $item['title'] ); ?>
</h3>
<?php if ( '' !== $item['excerpt'] ) : ?>
<p class="ds-service-card__excerpt">
<?php echo esc_html( $item['excerpt'] ); ?>
</p>
<?php endif; ?>
</a>
</article>
<?php endforeach; ?>
</div>
<?php
return (string) ob_get_clean();
}
add_shortcode(
'ds_service_cards',
'ds_render_service_cards_shortcode'
);
Otras Piezas del Mismo Sistema
Read-Only Portfolio Audit
Exporta Posts, Taxonomías, Medios, Polylang y Pretty Links a JSON con hash SHA-256 antes de migrar.
Bilingual Content Deployment
Crea contenidos independientes EN/ES, relaciona traducciones y configura Yoast y Medios desde WP-CLI.
Responsive Portfolio Rendering
Usa imágenes adaptables, carga diferida, decodificación asíncrona y prioridad explícita para el primer elemento.
PREGUNTAS FRECUENTES
Qué Demuestra este Portfolio de Código
¿Son ejemplos de código reales?
Sí. La página combina implementaciones procedentes de proyectos WordPress reales con muestras ampliadas de los patrones que utilizo para mostrar de forma legible validación, Seguridad, Arquitectura y automatización.
¿También trabajas con Gutenberg y Genesis?
Sí. Desarrollo sobre Gutenberg, Genesis Framework y Mai Engine cuando el proyecto lo requiere, utilizando Bloques, Hooks, filtros, CPT y componentes personalizados sin perder la experiencia editorial de WordPress.
¿Puedes trabajar sobre una instalación existente?
Sí. Primero reviso la Arquitectura, dependencias, datos, URLs, compatibilidad y riesgos. Después propongo el cambio mínimo que pueda mantenerse, probarse, verificarse y revertirse.
¿Qué incluye un proyecto de Desarrollo WordPress a Medida?
Puede incluir Plugins, Temas, CPT, Taxonomías, Gutenberg, WooCommerce, APIs, Polylang, WP-CLI, migraciones, Seguridad, Rendimiento, Technical SEO, Integraciones y documentación según la necesidad real.
¿TIENES UNA NECESIDAD TÉCNICA CONCRETA?
Cuéntame Qué Debe Hacer WordPress
Puedo revisar una instalación existente, diseñar la Arquitectura y desarrollar la pieza que falta. Trabajarás directamente con la persona que analiza, escribe, prueba y documenta el Código.









