Cesar Systems

Herramientas Informaticas

🚀 Corrección completa del control de lotes en inventario: evita duplicados, valida subcategorías y mejora la experiencia de usuario

Entrada fija

Resumen rápido: En este artículo revisamos y documentamos una corrección integral para un problema común en sistemas de inventario: la generación de lotes duplicados al agregar productos rápidamente vía AJAX. Incluye la solución frontend con un contador en memoria, la validación backend de subcategoría, manejo de errores con Toast, ejemplos de código listos para copiar y todo explicado paso a paso. Además se referencia el commit donde se subieron los cambios.


🧭 Introducción — ¿qué problema resolvimos y por qué importa?

En aplicaciones web que manejan inventario, es frecuente tener un flujo donde el usuario busca un producto y presiona Agregar varias veces. Si ese evento dispara una petición AJAX al servidor para obtener el último lote disponible y la respuesta se procesa en paralelo, existe una ventana de inconsistencia: varias respuestas pueden leer el mismo estado inicial y generar el mismo consecutivo, produciendo lotes duplicados (por ejemplo LMPLMLAPTOP000001 repetido varias veces). Esto provoca problemas reales: registros duplicados en inventario, conflictos de seguimiento, problemas legales en trazabilidad, y mala experiencia de usuario.

La meta fue sencilla pero crítica: evitar duplicados y hacer el flujo robusto sin reescribir toda la arquitectura. Priorizamos una solución práctica, compatible con jQuery y CodeIgniter/PHP, que puedas aplicar hoy en tu proyecto.


🔎 Diagnóstico técnico — por qué ocurrieron los duplicados

El error tiene tres factores principales:

  1. Asincronía: las peticiones AJAX regresan en tiempos distintos.
  2. Dependencia del DOM: el código calculaba el siguiente consecutivo consultando elementos del DOM que todavía no reflejaban las inserciones hechas por otras respuestas.
  3. Falta de validación del backend: el backend asumía que existían datos (por ejemplo, la subcategoría), lo que podía llevar a lotes mal formados o a errores silenciosos.

La combinación de esos puntos genera la race condition: varias respuestas ven el mismo último consecutivo y todas calculan el mismo siguiente valor.


🛠️ Solución aplicada — enfoque general

Optamos por una solución híbrida y pragmática:

  • Frontend: mantener un contador en memoria (lotesContador) que almacene, por cada loteBase, el último consecutivo usado en la sesión. Al recibir la respuesta del backend, sincronizamos ese contador con el consecutivo que trae el servidor y, si corresponde, lo incrementamos localmente para el siguiente lote. Esto evita depender del estado del DOM y evita conflictos cuando el usuario hace clicks rápidos.
  • Backend: agregar validaciones tempranas para condiciones críticas (por ejemplo: que el producto tenga subcategoría). Si falta información, el servidor devuelve una respuesta JSON con error: true y un message descriptivo.
  • UX: usar toasts para informar errores (icono de error), y evitar mensajes ambiguos o iconos de éxito cuando ocurrió un fallo.

💡 ¿Por qué esta solución es práctica?

Porque:

  • No requiere reestructurar la base de datos ni añadir infraestructura adicional.
  • Es fácil de integrar en proyectos existentes con jQuery + backend PHP/CodeIgniter.
  • Reduce la ventana de inconsistencias en la interfaz del usuario y evita la mayoría de los duplicados en escenarios de click rápido.
  • Permite seguir usando al backend como fuente de verdad cuando hay cambios externos: si el backend reporta un consecutivo mayor al local, el frontend se sincroniza.

🔧 Código: backend (CodeIgniter) — validar subcategoría y devolver JSON de error

Este fragmento se inserta en el controller que calcula el lote (método calculateLot() o similar). La idea es validar que exista idSubCategoria en la fila del producto y que la subcategoría realmente exista en la tabla de subcategorías. Si falla, devolvemos un JSON con error.

// Validar que tenga subcategoría
if (empty($productData["idSubCategoria"])) {
    return $this->response->setJSON([
        "error" => true,
        "message" => "El producto no tiene subcategoría asignada"
    ]);
}

// Buscar subcategoría
$subCategoryData = $this->subCategory->select("*")
        ->where("id", $productData["idSubCategoria"])
        ->first();

// Validar que exista
if (!$subCategoryData) {
    return $this->response->setJSON([
        "error" => true,
        "message" => "Subcategoría no encontrada"
    ]);
}

$keyCategory = $subCategoryData["descripcion"];
// ... continuar con la construcción de $baseLot, búsqueda del último lote y generación del consecutivo ...

Con esto, si alguien intenta calcular un lote para un producto sin subcategoría, el frontend recibirá un JSON con error y se podrá mostrar un mensaje al usuario en lugar de producirse un lote inválido o un fallo más profundo.


🖥️ Código: frontend (jQuery) — patrón robusto para decidir loteFinal

A continuación está el patrón que implementamos en el success del AJAX. Debes declarar var lotesContador = {}; en un scope compartido (por ejemplo, al inicio de tu archivo JS o dentro de tu $(function(){...})), y luego usar esta lógica al recibir la respuesta del servidor para calcular el loteFinal.

/* Declarar al inicio del script */
var lotesContador = {};

/* Dentro del success del AJAX */
if (respuesta.error) {
    Toast.fire({
        icon: 'error',
        title: respuesta.message
    });
    return;
}

var loteCalculado = (respuesta && respuesta.lot) ? respuesta.lot : (lote || "UNKNOWN000001");

if (!loteCalculado) {
    console.error("No se pudo determinar el lote calculado");
    return;
}

var loteBase = loteCalculado.slice(0, -6);  // asume 6 dígitos de consecutivo
var consecutivoBackend = parseInt(loteCalculado.slice(-6), 10);
if (isNaN(consecutivoBackend)) consecutivoBackend = 0;

var loteFinal;

if (!lotesContador.hasOwnProperty(loteBase)) {
    // Primera vez: inicializar con lo que trae el backend
    lotesContador[loteBase] = consecutivoBackend;
    loteFinal = loteCalculado;
} else {
    // Ya hay un contador local
    if (consecutivoBackend > lotesContador[loteBase]) {
        // Backend avanzó por fuera de esta sesión: sincronizamos
        lotesContador[loteBase] = consecutivoBackend;
        loteFinal = loteCalculado;
    } else {
        // Usamos el siguiente consecutivo en memoria
        lotesContador[loteBase] = lotesContador[loteBase] + 1;
        var nuevo = String(lotesContador[loteBase]).padStart(6, "0");
        loteFinal = loteBase + nuevo;
    }
}

/* Llamar a la función que agrega el renglón con loteFinal */
agregarRenglon( idProduct, codeProduct, loteFinal, description, salePrice,
    porcentTax, porcentIVARetenido, porcentISRRetenido,
    claveUnidadSAT, unidad, claveProductoSAT );

Este flujo asegura que:

  • Si el backend ya avanzó el consecutivo en otra sesión, el frontend se sincroniza.
  • Si no, el frontend incrementa localmente el consecutivo para evitar duplicados cuando se hagan clicks rápidos.

📋 Función agregarRenglon — ejemplo práctico

Aquí tienes una versión robusta de tu función que construye el HTML del renglón y normaliza valores numéricos para evitar NaN o inputs inválidos:

function agregarRenglon(idProduct, codeProduct, lote, description, salePrice,
    porcentTax, porcentIVARetenido, porcentISRRetenido,
    claveUnidadSAT, unidad, claveProductoSAT) {

    salePrice = Number(salePrice) || 0;
    porcentTax = Number(porcentTax) || 0;
    porcentIVARetenido = Number(porcentIVARetenido) || 0;
    porcentISRRetenido = Number(porcentISRRetenido) || 0;

    var tax = (porcentTax > 0) ? ((porcentTax * 0.01) * salePrice) : 0;
    var IVARetenido = (porcentIVARetenido > 0) ? ((porcentIVARetenido * 0.01) * salePrice) : 0;
    var ISRRetenido = (porcentISRRetenido > 0) ? ((porcentISRRetenido * 0.01) * salePrice) : 0;
    var neto = (((porcentTax * 0.01) + 1) * salePrice) - (IVARetenido + ISRRetenido);

    var renglon = "<div class='form-group row nuevoProduct'>";
    renglon += "<div class='col-1'>";
    renglon += "<button type='button' class='btn btn-danger quitProduct'><span class='far fa-trash-alt'></span></button>";
    renglon += " <button type='button' data-toggle='modal' data-target='#modelMoreInfoRow' class='btn btn-primary btnInfo'><span class='fa fa-fw fa-pencil-alt'></span></button> ";
    renglon += "<input type='hidden' class='idProductR' name='idProductR' value='" + (idProduct || "") + "'>";
    renglon += "</div>";
    renglon += "<div class='col-1'>";
    renglon += "<input type='hidden' class='claveProductoSATR' name='claveProductoSATR' value='" + (claveProductoSAT || "") + "'>";
    renglon += "<input type='hidden' class='claveUnidadSatR' name='claveUnidadSatR' value='" + (claveUnidadSAT || "") + "'>";
    renglon += "<input type='hidden' class='unidad' name='unidad' value='" + (unidad || "") + "'>";
    renglon += "<input type='text' class='form-control codeProduct' name='codeProduct' value='" + (codeProduct || "") + "'> </div>";
    renglon += "<div class='col-1'> <input type='text' class='form-control lote' name='lote' value='" + (lote || "") + "' required> </div>";
    renglon += "<div class='col-6'> <input type='text' class='form-control description' name='description' value='" + (description || "") + "' required> </div>";
    renglon += "<div class='col-1'> <input type='number' class='form-control cant' name='cant' value='1' required>";
    renglon += "<input type='hidden' class='porcentIVARetenido' name='porcentIVARetenido' value='" + porcentIVARetenido + "'>";
    renglon += "<input type='hidden' class='porcentISRRetenido' name='porcentISRRetenido' value='" + porcentISRRetenido + "'>";
    renglon += "<input type='hidden' class='porcentTax' name='porcentTax' value='" + porcentTax + "'></div>";
    renglon += "<div class='col-1'> <input type='number' class='form-control price' name='price' value='" + salePrice + "' required>";
    renglon += "<input type='hidden' class='IVARetenido' name='IVARetenido' value='" + IVARetenido + "'>";
    renglon += "<input type='hidden' class='ISRRetenido' name='ISRRetenido' value='" + ISRRetenido + "'>";
    renglon += "<input type='hidden' class='tax' name='tax' value='" + tax + "'> </div>";
    renglon += "<div class='col-1'> <input readonly type='number' class='form-control total' name='total' value='" + salePrice + "'>";
    renglon += "<input type='hidden' class='neto' name='neto' value='" + neto + "'> </div>";
    renglon += "</div>";

    $('.rowProducts').append(renglon);

    if (typeof listProducts === "function") listProducts();
}

✅ Manejo de errores y UX — toasts y mensajes claros

Una buena UX evita frustración. Cuando el backend devuelve error: true, mostramos un toast con icono de error y el mensaje específico:

if (respuesta.error) {
    Toast.fire({
        icon: 'error',
        title: respuesta.message
    });
    return;
}

Observa dos puntos importantes:

  • No uses icon: 'success' para errores.
  • Mantén los mensajes del backend claros y útiles para el usuario (por ejemplo: “El producto no tiene subcategoría asignada”).

🧪 Pruebas que debes ejecutar (y por qué)

Antes de subir a producción, ejecuta una batería de pruebas que validen los casos reales:

  1. Clicks rápidos: simula spam click y verifica que los lotes generados en el DOM sean secuenciales y no repetidos.
  2. Producto sin subcategoría: intenta agregar un producto con idSubCategoria vacío o inexistente y verifica que aparezca el toast con el mensaje correcto y que no se agregue el renglón.
  3. Multiusuario: abre dos sesiones diferentes (o dos navegadores) y agrega productos con la misma loteBase para comprobar la sincronización con el backend.
  4. Eliminar renglón: borra renglones y agrega nuevos; el contador en memoria no se decrementa (esto evita reuso accidental de consecutivos), verifica que los nuevos lotes aumenten correctamente.
  5. Prueba de estrés: con un pequeño script o con herramientas como k6 o ab, simula múltiples peticiones al endpoint getLastLot y verifica que el backend responda correctamente y que el frontend se comporte como esperado.

⚠️ Consideraciones y limitaciones de la solución

La solución de contador en memoria es práctica y resuelve el problema en la mayoría de los casos, pero existen condiciones donde se requiere mayor robustez:

  • Escalabilidad y clusters distribuidos: si tu aplicación corre en múltiples servidores y hay concurrente masiva, el contador en memoria por sesión no garantiza unicidad global. En ese caso, considera un servicio centralizado (Redis, tabla con contador atómico) para emitir consecutivos atómicos.
  • Transacciones críticas: si la generación del lote debe ser 100% atómica con la inserción del registro en inventario, la lógica debe moverse al backend con locking o transacción que reserve el siguiente número y lo persista en la misma operación.
  • Reutilización de números: en esta solución NO se reutilizan consecutivos al borrar renglones. Reusar números es peligroso porque puede llevar a duplicados e inconsistencias en auditoría. Si requieres reuso, debe implementarse cuidadosamente en el backend y con auditoría.

🔁 Alternativas y mejoras (sin la sección “Siguiente paso”)

Aquí describimos varias alternativas que puedes implementar según tu nivel de exigencia:

1) Redis / contador centralizado

Usar INCR en Redis para obtener el siguiente consecutivo de forma atómica. Ideal para arquitecturas distribuidas. Ventajas: simple y rápido. Inconveniente: requiere infra adicional.

2) Tabla en BD con fila de contador y SELECT FOR UPDATE

Usar una tabla con el contador y reservar el número con locking a nivel de transacción. Ventajas: no necesitas nueva infra; Inconveniente: puede ser más lento bajo alta concurrencia y requiere diseño cuidadoso para evitar contención.

3) Generación optimista y reconciliación

Generas un lote provisional en frontend y al persistir en backend verificas si está disponible. Si no, el backend retorna el lote correcto y el frontend actualiza el renglón. Esto requiere un flujo UX para reconciliación (indicar “lote pendiente” y luego “confirmado”).


📦 Registro de cambios — commit en tu repositorio

Los cambios relacionados con esta corrección se subieron al repositorio en el commit referenciado a continuación. Revisa el diff para ver exactamente qué archivos y líneas se modificaron.

Commit con los cambios: :contentReference[oaicite:0]{index=0}


📌 Checklist completo para despliegue (rápido)

  • ✅ Aplicar validación de idSubCategoria en backend.
  • ✅ Devolver error:true y message si falta subcategoría.
  • ✅ Implementar lotesContador en frontend y sincronizar con backend.
  • ✅ Mostrar Toast con icon: 'error' cuando haya problemas.
  • ✅ Ejecutar pruebas de clicks rápidos y multiusuario.
  • ✅ Instrumentar logs en backend para cada lote generado (auditoría).
  • ✅ No decrementar el contador local al borrar renglones.
  • ✅ Revisar la necesidad de un contador global (Redis/BD) según volumen de concurrencia.

🔍 Ejemplos de mensajes útiles para el backend

  • El producto no tiene subcategoría asignada — cuando idSubCategoria está vacío.
  • Subcategoría no encontrada — cuando el ID no existe en la tabla de subcategorías.
  • No se pudo obtener el último lote — cuando la consulta a la tabla de saldos falla.

Mensajes claros hacen más fácil el debugging y mejoran la experiencia del desarrollador y del usuario final.


🧾 Buenas prácticas y recomendaciones finales

  1. Fail fast: valida temprano en backend y devuelve errores claros.
  2. Fuente de verdad: el backend siempre es la fuente de verdad; el frontend optimiza UX y reduce latencia.
  3. Auditoría: registra cada vez que se asigna un lote para trazar operaciones en caso de discrepancias.
  4. Pruebas automáticas: escribe pruebas e2e que cubran clicks rápidos y multiusuario.
  5. Documentación: documenta en el README del repo el comportamiento del contador en memoria y las condiciones que requieren un contador centralizado.

🔚 Conclusión

Resolver la duplicación de lotes en un sistema de inventario es crítico para mantener la integridad de los datos y la confianza del usuario. La estrategia que aplicamos —validación temprana en backend y un contador en memoria sincronizado en frontend— ofrece una solución práctica, rápida de implementar y efectiva para la gran mayoría de escenarios. Para entornos de alta concurrencia o arquitecturas distribuidas, existe la opción de aumentar la robustez con contadores atómicos centralizados (Redis, BD con locking).

Si quieres que convierta esta guía en un artículo formateado para tu tema de WordPress (por ejemplo con estilos específicos o bloques personalizados), o que genere una versión en Markdown para tu repo, lo preparo. Solo pégalo en un bloque HTML personalizado y debería quedar correcto en Gutenberg.

Artículo generado para facilitar la integración de correcciones en el módulo de inventario. Copia y pega en tu editor de WordPress (bloque HTML personalizado) y listo. ✅

🎬 Auto Uploader de YouTube Shorts con IA (Ollama)

Entrada fija

🎬 Auto Uploader de YouTube Shorts con IA (Ollama)

Script en Python que automatiza completamente la subida de YouTube Shorts.

El sistema:

  • Detecta videos nuevos en una carpeta
  • Usa IA local con Ollama para generar título, descripción y hashtags
  • Sube automáticamente el video a YouTube
  • Mueve el archivo a la carpeta de subidos
  • Espera un tiempo aleatorio antes de subir el siguiente video

Todo funciona 100% local sin depender de APIs externas de IA.


🚀 Características

  • ✔ Generación automática de metadata con IA local
  • ✔ Subida automática a YouTube Shorts
  • ✔ Espera aleatoria entre subidas
  • ✔ Manejo automático de tokens de YouTube
  • ✔ Sistema simple basado en carpetas
  • ✔ No requiere servicios en la nube para IA
  • ✔ Compatible con Linux, Mac y Windows (WSL)

📂 Estructura de Carpetas

El script utiliza la siguiente estructura:


~/shorts/

global/
   inbox/
      video1.mp4
      video2.mp4

   uploaded/
      video1.mp4
      video2.mp4

📥 inbox

Aquí se colocan los videos que se quieren subir.

📤 uploaded

Los videos que ya fueron subidos se moverán automáticamente aquí.


⚙️ Requisitos

Python

Python 3.9 o superior

Instalar dependencias:

pip install google-api-python-client google-auth-oauthlib google-auth-httplib2

Ollama (IA local)

Instalar Ollama:

https://ollama.com

Descargar el modelo usado por el script:

ollama pull qwen2.5:14b

Modelo usado:

qwen2.5:14b

Este modelo genera:

  • títulos optimizados
  • descripciones
  • hashtags

sin necesidad de internet.


🔑 Credenciales de YouTube

Debes crear credenciales OAuth en Google Cloud.

  1. Ir a Google Cloud Console
  2. Crear un proyecto
  3. Activar la API:
YouTube Data API v3
  1. Crear credenciales:
OAuth Client ID

Tipo:

Desktop Application

Descargar el archivo:

client_secrets.json

Colócalo en la misma carpeta que el script.


📦 Instalación

1️⃣ Guardar el script

uploader.py

2️⃣ Crear carpetas


mkdir -p ~/shorts/global/inbox
mkdir -p ~/shorts/global/uploaded

3️⃣ Instalar dependencias

pip install google-api-python-client google-auth-oauthlib

4️⃣ Instalar modelo de IA

ollama pull qwen2.5:14b

▶️ Uso

1️⃣ Colocar videos en:

~/shorts/global/inbox

Ejemplo:


drum_fill_01.mp4
drum_solo_fast.mp4

2️⃣ Ejecutar el script

python3 uploader.py

3️⃣ Primer inicio

Se abrirá una ventana de login de Google.

Después de iniciar sesión se generará:

token.json

Ese archivo permitirá subir videos sin volver a iniciar sesión.


🧠 Cómo funciona


Video nuevo
      │
      ▼
IA genera metadata
      │
      ▼
Subida a YouTube
      │
      ▼
Mover a carpeta uploaded
      │
      ▼
Esperar tiempo aleatorio

⏱ Sistema Anti-Spam

El script espera un tiempo aleatorio entre videos:

200 a 900 segundos

Esto equivale aproximadamente a:

3 a 15 minutos

Esto ayuda a evitar comportamientos detectables como automatización masiva.


🧠 Generación de Metadata con IA

El script usa Ollama local para generar contenido optimizado para Shorts.

Ejemplo de salida:


{
  "title": "Fill de batería rápido 🔥",
  "description": "Un fill explosivo en batería.\n¿Puedes tocarlo?",
  "hashtags": ["#bateria", "#drums", "#drummer", "#shorts", "#musica"]
}

🎯 Por qué usar este sistema

1️⃣ Automatización total

Puedes subir decenas o cientos de Shorts automáticamente.

2️⃣ IA local

  • No dependes de OpenAI
  • No pagas APIs
  • No hay límites de uso

3️⃣ Ideal para contenido masivo

Perfecto para:

  • músicos
  • creadores de contenido
  • clips de gaming
  • podcasts
  • contenido educativo

4️⃣ Ahorra tiempo

Subir manualmente muchos videos puede tomar horas.

Este sistema puede hacerlo automáticamente.

5️⃣ Escalable

Puedes expandirlo para:

  • TikTok
  • Instagram Reels
  • Facebook Reels
  • múltiples cuentas

🔧 Configuración del Script


BASE_DIR = "~/shorts"

OLLAMA_MODEL = "qwen2.5:14b"

VIDEO_EXTS = (".mp4", ".mov", ".mkv")

📊 Logs del Sistema


[2026-03-10 12:22:01] Procesando: drum_fill.mp4
[2026-03-10 12:22:04] Metadata generada
[2026-03-10 12:22:12] YouTube OK → videoId=abc123
[2026-03-10 12:22:12] Video movido a uploaded
[2026-03-10 12:22:12] Esperando 10 minutos...

⚠️ Posibles Errores

Ollama no instalado

Ollama error

Solución:

ollama install

client_secrets.json no encontrado

YouTube error: client_secrets.json no encontrado

Debes descargar las credenciales de Google Cloud.


🔒 Seguridad

El sistema guarda un archivo:

token.json

Este archivo contiene el acceso a tu cuenta de YouTube.

No lo compartas.


🧩 Posibles mejoras

  • Subir a TikTok automáticamente
  • Generar miniaturas
  • Generar títulos A/B
  • Programar horarios de publicación
  • Subir a múltiples canales

🪰🧠 El cerebro virtual de una mosca: el experimento que podría cambiar la neurociencia

Entrada fija

🪰🧠 El cerebro virtual de una mosca: el experimento que podría cambiar la neurociencia

En 2026 ocurrió algo que muchos científicos consideraban ciencia ficción: un cerebro completo de un animal fue recreado dentro de una computadora y conectado a un cuerpo virtual.

Lo más sorprendente es que este cerebro digital comenzó a comportarse como una mosca real sin haber sido entrenado con inteligencia artificial.

Este experimento demuestra algo muy importante:

el comportamiento puede surgir directamente de la estructura del cerebro.

En este artículo aprenderás:

  • 🧠 Qué es el conectoma de la mosca
  • 🪰 Cómo se creó un cerebro virtual
  • 🕹 Cómo se conectó a un cuerpo virtual
  • 🤖 Por qué el sistema funcionó sin entrenamiento
  • 🔬 Qué significa este avance para la inteligencia artificial

🧠 El cerebro de la mosca: pequeño pero extremadamente complejo

La especie más utilizada en investigación es Drosophila melanogaster, conocida como la mosca de la fruta.

Aunque parece un insecto simple, su cerebro tiene una estructura sorprendente.

  • 🧠 alrededor de 125,000 a 140,000 neuronas
  • 🔗 cerca de 50 millones de conexiones neuronales
  • ⚡ circuitos especializados para visión, olfato y movimiento

Comparación con otros cerebros

AnimalNúmero de neuronas
Mosca~140,000
Ratón~75 millones
Humano~86 mil millones

A pesar de su tamaño, las moscas pueden:

  • volar con gran precisión
  • evitar obstáculos
  • buscar comida
  • aprender asociaciones simples

Esto demuestra que incluso cerebros pequeños pueden generar comportamientos complejos.


🔬 Qué es el conectoma

Para entender cómo funciona un cerebro, los científicos necesitan conocer todas las conexiones entre neuronas.

Ese mapa completo se llama conectoma.

Un conectoma muestra:

  • cada neurona
  • cada conexión entre neuronas
  • la dirección de las señales

Es básicamente el diagrama eléctrico completo del cerebro.


🧬 El proyecto que mapeó el cerebro de la mosca

Uno de los proyectos más importantes fue FlyWire.

Este proyecto utilizó:

  • microscopía electrónica
  • inteligencia artificial
  • millones de imágenes microscópicas

Los investigadores cortaron el cerebro en miles de secciones microscópicas y reconstruyeron todas las neuronas.

El resultado fue el primer mapa completo del cerebro de un insecto complejo.


🖥 Cómo recrearon el cerebro dentro de una computadora

Una vez que los científicos obtuvieron el conectoma, el siguiente paso fue convertirlo en un modelo digital.

El proceso fue:

  1. Cada neurona se convirtió en una neurona digital
  2. Cada conexión sináptica fue replicada
  3. Se simuló la actividad eléctrica neuronal

El modelo final contenía aproximadamente:

  • 125,000 neuronas simuladas
  • 50 millones de sinapsis

Las neuronas se modelaron con sistemas matemáticos que imitan cómo disparan impulsos eléctricos.


🕹 Conectar el cerebro digital a un cuerpo virtual

El cerebro digital fue conectado a un cuerpo virtual en un entorno físico simulado.

Para ello se utilizó un sistema llamado NeuroMechFly.

Este sistema simula:

  • el esqueleto de la mosca
  • los músculos
  • las articulaciones
  • los sensores

Además se utilizó el motor físico MuJoCo para calcular movimiento, gravedad y fricción.


🔁 El ciclo cerebro-cuerpo

El sistema funciona como un organismo real mediante un bucle cerrado.

1. El entorno genera estímulos

  • objetos
  • olores
  • obstáculos

2. El cerebro procesa la información

Las señales viajan por las neuronas digitales como en un cerebro real.

3. El cerebro envía órdenes motoras

Las neuronas motoras activan músculos virtuales.

4. El cuerpo virtual se mueve

La mosca virtual puede caminar, girar y explorar.

5. Se generan nuevos estímulos

El movimiento cambia lo que la mosca ve y huele, reiniciando el ciclo.


🤯 Lo más sorprendente: no hubo entrenamiento

En la mayoría de sistemas de inteligencia artificial es necesario entrenar al modelo con grandes cantidades de datos.

Pero en este experimento ocurrió algo distinto:

el comportamiento emergió automáticamente.

Los científicos simplemente replicaron la estructura neuronal real.

Esto demuestra que muchas conductas están codificadas directamente en el cableado del cerebro.


🪰 Qué hizo la mosca virtual

Cuando el cerebro digital se conectó al cuerpo virtual, el sistema comenzó a mostrar comportamientos naturales.

  • caminar
  • explorar el entorno
  • limpiar sus patas
  • reaccionar a estímulos

En muchos casos el comportamiento coincidía con el de moscas reales.


🧠 Qué significa esto para la inteligencia artificial

Este experimento podría cambiar la forma en que se diseñan sistemas inteligentes.

Actualmente la IA depende principalmente de entrenamiento con grandes conjuntos de datos.

Pero este experimento sugiere que la arquitectura neuronal también puede generar inteligencia por sí misma.


🤖 ¿Podría hacerse con cerebros más grandes?

Después de la mosca, los científicos quieren simular cerebros más complejos.

CerebroNúmero de neuronas
Mosca~140,000
Ratón~70 millones
Humano~86 mil millones

El cerebro humano es aproximadamente 600,000 veces más grande que el de una mosca.

Simular algo así requeriría una potencia computacional enorme.


🧠 Conclusión

La recreación digital del cerebro de una mosca representa uno de los avances más importantes en neurociencia.

Por primera vez un cerebro completo fue:

  • simulado digitalmente
  • conectado a un cuerpo virtual
  • capaz de generar comportamiento natural

Este experimento demuestra que gran parte del comportamiento está codificado en la estructura del cerebro.

Aunque todavía estamos muy lejos de simular cerebros humanos completos, este avance podría ayudarnos a entender cómo surge la inteligencia y la mente.

La mosca virtual puede parecer pequeña… 🪰

pero podría ser el primer paso hacia comprender cómo funciona la mente en el universo. 🧠✨

🔒 Cómo validar documentos copiados en SAP Business One usando SBO_SP_TransactionNotification (Guía completa)

Entrada fija

🔒 Cómo validar documentos copiados en SAP Business One usando SBO_SP_TransactionNotification (Guía completa)

En SAP Business One es muy común que los usuarios copien documentos para agilizar procesos. Por ejemplo, copiar una Salida de Mercancía y convertirla en una Entrada de Mercancía.

Sin embargo, esto puede generar un problema serio: los usuarios podrían modificar las líneas del documento (cambiar cantidades, precios o incluso eliminar artículos) antes de guardar.

Esto rompe la integridad de los datos del inventario.

La buena noticia es que SAP Business One permite evitar esto usando el procedimiento almacenado SBO_SP_TransactionNotification.

En esta guía aprenderás:

  • ✅ Qué es SBO_SP_TransactionNotification
  • ✅ Cómo validar documentos copiados
  • ✅ Cómo impedir cambios en líneas
  • ✅ Cómo evitar eliminar o agregar artículos
  • ✅ Cómo detectar si un documento fue copiado desde otro
  • ✅ Ejemplo completo listo para usar

📌 ¿Qué es SBO_SP_TransactionNotification?

SBO_SP_TransactionNotification es un procedimiento almacenado que se ejecuta automáticamente cada vez que se crea, actualiza o elimina un documento en SAP Business One.

Esto permite validar reglas de negocio personalizadas antes de que el documento se guarde.

Por ejemplo:

  • 🚫 Bloquear precios incorrectos
  • 🚫 Impedir documentos incompletos
  • 🚫 Validar campos obligatorios
  • 🚫 Evitar cambios en documentos copiados

Si una validación falla, SAP muestra un mensaje al usuario y el documento no se guarda.


🏗️ Cómo funciona la copia de documentos en SAP Business One

Cuando copias un documento en SAP Business One, el sistema guarda referencias al documento original.

Por ejemplo:

  • Salida de mercancía → Entrada de mercancía

Las tablas involucradas son:

DocumentoEncabezadoDetalle
Salida de mercancíaOIGEIGE1
Entrada de mercancíaOIGNIGN1

La relación entre documentos se guarda en las líneas del documento destino.


🔗 Campos que conectan los documentos

Cuando un documento se copia, SAP guarda estos campos en la tabla del detalle:

CampoDescripción
BaseEntryDocEntry del documento origen
BaseLineLínea del documento origen
BaseTypeTipo de documento origen
BaseRefNúmero visible del documento origen

Esto permite reconstruir la relación entre documentos.


📊 Ejemplo de relación entre documentos

Supongamos que existe esta salida de mercancía:

OIGE
DocEntry = 120
DocNum = 4500

Detalle:

IGE1
DocEntry = 120
LineNum = 0
ItemCode = ITEM001
Quantity = 5

Luego se crea una entrada copiando ese documento:

IGN1
DocEntry = 300
BaseEntry = 120
BaseLine = 0
BaseType = 60

Esto indica que la entrada proviene de esa salida.


⚠️ Problema común en SAP Business One

Cuando un usuario copia un documento, puede modificar datos antes de guardarlo:

  • ✏️ Cambiar cantidades
  • ✏️ Cambiar precios
  • ✏️ Eliminar líneas
  • ✏️ Agregar artículos

Esto puede provocar inconsistencias en inventario y contabilidad.


🎯 Objetivo de nuestra validación

La validación debe impedir:

  • ❌ eliminar líneas
  • ❌ agregar líneas
  • ❌ cambiar artículo
  • ❌ cambiar descripción
  • ❌ cambiar cantidad
  • ❌ cambiar precio
  • ❌ cambiar almacén
  • ❌ cambiar centro de costo

Pero solo cuando el documento proviene de una Salida de Mercancía.


🧠 Detectar si un documento fue copiado

La forma más sencilla es revisar si el campo BaseEntry tiene valor.

SELECT BaseEntry
FROM IGN1
WHERE DocEntry = :DocEntry

Si el valor existe, el documento proviene de otro.


🧾 Código completo de validación

IF :object_type = '59' AND (:transaction_type = 'A' OR :transaction_type = 'U') THEN

DECLARE v_base_doc INT;
DECLARE v_lineas_base INT;
DECLARE v_lineas_doc INT;
DECLARE v_changes INT;

SELECT TOP 1 "BaseEntry"
INTO v_base_doc
FROM "IGN1"
WHERE "DocEntry" = :list_of_cols_val_tab_del;

IF v_base_doc IS NOT NULL THEN

SELECT COUNT(*)
INTO v_lineas_base
FROM "IGE1"
WHERE "DocEntry" = v_base_doc;

SELECT COUNT(*)
INTO v_lineas_doc
FROM "IGN1"
WHERE "DocEntry" = :list_of_cols_val_tab_del;

IF v_lineas_base <> v_lineas_doc THEN
error := -9200;
error_message := 'No se permite eliminar o agregar lineas del documento copiado.';
END IF;

SELECT COUNT(*)
INTO v_changes
FROM "IGN1" A
JOIN "IGE1" B
ON A."BaseEntry" = B."DocEntry"
AND A."BaseLine" = B."LineNum"
WHERE A."DocEntry" = :list_of_cols_val_tab_del
AND (
IFNULL(A."ItemCode",'') <> IFNULL(B."ItemCode",'')
OR IFNULL(A."Dscription",'') <> IFNULL(B."Dscription",'')
OR IFNULL(A."Quantity",0) <> IFNULL(B."Quantity",0)
OR IFNULL(A."Price",0) <> IFNULL(B."Price",0)
OR IFNULL(A."WhsCode",'') <> IFNULL(B."WhsCode",'')
OR IFNULL(A."OcrCode",'') <> IFNULL(B."OcrCode",'')
);

IF v_changes > 0 THEN
error := -9201;
error_message := 'No se permite modificar las lineas del documento base.';
END IF;

END IF;

END IF;

🧪 Cómo probar la validación

Para verificar que todo funcione:

  1. Crear una salida de mercancía
  2. Copiarla a entrada de mercancía
  3. Intentar cambiar cantidad
  4. Intentar borrar una línea
  5. Intentar agregar una línea

Si la validación funciona correctamente, SAP mostrará un mensaje de error.


💡 Consejos profesionales

Los consultores SAP suelen aplicar estas recomendaciones:

  • 🔹 Validar siempre BaseType
  • 🔹 Comparar líneas con BaseLine
  • 🔹 Bloquear cambios críticos
  • 🔹 Usar mensajes claros para el usuario

🚀 Beneficios de esta validación

  • ✔ Evita errores humanos
  • ✔ Protege la integridad del inventario
  • ✔ Mantiene trazabilidad entre documentos
  • ✔ Mejora auditorías
  • ✔ Reduce inconsistencias contables

📚 Conclusión

El procedimiento SBO_SP_TransactionNotification es una herramienta poderosa para implementar reglas de negocio en SAP Business One.

Con la validación adecuada puedes garantizar que los documentos copiados mantengan exactamente la misma información que el documento original.

Esto mejora la calidad de los datos, reduce errores y asegura que el inventario refleje la realidad operativa de la empresa.

Si trabajas con SAP Business One, dominar este procedimiento es una habilidad clave para cualquier consultor o desarrollador.

💡 Implementar controles como este puede ahorrar muchos problemas en producción.

🚀 Generador Automático de CRUD para CodeIgniter 4: Crea Módulos Completos en 1 Minuto 🔥

Entrada fija





Generador Automático de CRUD para CodeIgniter 4 – julio101290/boilerplate

🚀 Generador Automático de CRUD para CodeIgniter 4: Crea Módulos Completos en 1 Minuto 🔥

¿Cansado de escribir el mismo código una y otra vez? ¿Tus proyectos se retrasan por la tediosa creación de modelos, controladores y vistas? ¡Tenemos la solución! Te presento el Generador Automático de CRUD para CodeIgniter 4, una herramienta integrada en mi boilerplate que transforma una tabla de base de datos en un módulo funcional, seguro y profesional en menos de 60 segundos. Ahorra cientos de horas y olvídate de los errores repetitivos.


📌 Índice


🎯 ¿Qué es este generador?

Es un controlador inteligente que, a partir del nombre de una tabla existente en tu base de datos, genera de forma automática todos los archivos necesarios para un CRUD completo:

  • ✅ Modelo con validaciones y soft delete
  • ✅ Controlador con DataTables server-side
  • ✅ Vistas (listado + modal) integradas con AdminLTE
  • ✅ Archivos de idioma (inglés y español)
  • ✅ Migración lista para ejecutar
  • ✅ Rutas listas para copiar o integradas en tu paquete
  • ✅ Permisos creados automáticamente (RBAC)

Todo esto con código limpio, indentado y siguiendo las mejores prácticas.

💡 Dato curioso: El generador lee la estructura de tu tabla y adapta los campos automáticamente. Si tu tabla tiene campos como created_at, updated_at, deleted_at, los maneja de forma especial para que el soft delete funcione perfectamente.

🏗️ El entorno perfecto: julio101290/boilerplate

Este generador vive dentro de mi fork del excelente boilerplate de agungsugiarto, adaptado y mejorado para proyectos reales. Incluye:

  • 🎨 AdminLTE 3 + Bootstrap 4 + Font Awesome 5
  • 🔐 RBAC completo con Myth/Auth
  • 📊 Menú dinámico generado desde BD
  • 🌍 Internacionalización (EN, ES, ID)
  • Instalación por Composer y comandos spark

👉 Repositorio oficial en GitHub

⚙️ Características principales

CaracterísticaBeneficio
Generación instantáneaDe 1 hora a 1 minuto por módulo
Código seguroCSRF manejado automáticamente
Validación por defectoEvita errores comunes
Soft delete funcionaldeleted_at siempre NULL al guardar
Soporte multi-empresaFiltrado por idEmpresa incluido
Modo App / VendorGenera en app/ o en tu paquete Composer
Permisos integradosSeeder actualizado automáticamente

🔧 Mejoras de seguridad y robustez

Hemos recorrido un largo camino desde la primera versión. Aquí están las mejoras clave que marcan la diferencia:

🛡️ Seguridad CSRF sin dolores de cabeza

El error 403 por CSRF era el más común al trabajar con AJAX. Ahora:

  • 🔹 El controlador devuelve el nuevo token en cada respuesta (éxito o error).
  • 🔹 La vista lee el token del campo oculto y lo envía en el FormData.
  • 🔹 Tras cada petición, se actualiza el token en el campo y la metaetiqueta.
  • 🔹 Incluso en errores de validación, el token se renueva, permitiendo reenviar el formulario sin recargar la página.

📌 Ejemplo de respuesta del controlador:

return $this->respond([
    'status'    => 201,
    'message'   => 'Guardado correctamente',
    'csrf_hash' => csrf_hash() // 👈 Nuevo token
], 201);

🗑️ El misterio de deleted_at resuelto

Problema: los registros nuevos aparecían como eliminados porque deleted_at se llenaba con 0000-00-00.... Solución en dos capas:

  • ✅ En el modelo: deleted_at excluido de $allowedFields y eventos beforeInsert/beforeUpdate que lo eliminan.
  • ✅ En el controlador: unset($datos['deleted_at']) antes de guardar.

Resultado: deleted_at siempre NULL en altas y modificaciones, y solo el método delete() lo actualiza.

✅ Validación automática para idEmpresa

Casi todas las tablas dependen de una empresa. Ahora el modelo incluye por defecto:

protected $validationRules = [
    'idEmpresa' => 'required|integer|greater_than[0]'
];

¡Olvídate de registros huérfanos!

📦 Genera CRUDs dentro de paquetes vendor

¿Desarrollas paquetes reutilizables? El generador puede crear el CRUD directamente en tu paquete Composer:

http://tusitio.com/generateCRUDComposer/mi_tabla?target=vendor&package=tu/paquete
  • 🔹 Lee el composer.json y extrae el namespace PSR-4 automáticamente.
  • 🔹 Crea la estructura src/Models, src/Controllers, etc.
  • 🔹 Actualiza el archivo src/Config/Routes.php del paquete con las nuevas rutas.
  • 🔹 Tu paquete se vuelve autónomo y portable.

🔑 Permisos gestionados como profesionales

Antes: los permisos se creaban en caliente al generar el CRUD (poco ortodoxo). Ahora: cuando el destino es un paquete vendor, el generador actualiza el Seeder correspondiente (ej. BoilerplateCFDIDescargaMasiva.php), añadiendo la línea para crear el permiso y asignarlo al admin.

Así, la instalación de permisos se hace como Dios manda: con php spark db:seed.

⏱️ Ahorro de tiempo real

TareaSin generadorCon generadorAhorro
CRUD de 10 campos45-60 min1 min~98%
20 tablas por proyecto15-20 horas20 minutos¡Días!

Ese tiempo lo puedes reinvertir en lógica de negocio que realmente aporta valor. Además, todo el código generado sigue el mismo patrón, reduciendo la deuda técnica y facilitando el mantenimiento.

📝 Código completo del generador

Aquí tienes la clase AutoCrudControllerComposer en su versión final. Cópiala directamente en tu proyecto (julio101290/boilerplate/Controllers/).

db = \Config\Database::connect();
        $this->authorize = Services::authorization();
        $this->users = new UserModel();
        helper('utilerias');
    }

    /**
     * Método principal para generar el CRUD
     *
     * @param string      $table          Nombre de la tabla
     * @param string|null $targetType     'app' o 'vendor' (por GET)
     * @param string|null $vendorPackage  Paquete vendor
     * @param string|null $vendorNamespace Namespace (auto-detected)
     */
    public function index($table, $targetType = null, $vendorPackage = null, $vendorNamespace = null)
    {
        // Leer de GET si no se pasaron como argumentos
        if ($targetType === null) {
            $targetType = $this->request->getGet('target') ?? 'app';
        }
        if ($vendorPackage === null && $targetType === 'vendor') {
            $vendorPackage = $this->request->getGet('package');
        }
        if ($vendorNamespace === null && $targetType === 'vendor') {
            $vendorNamespace = $this->request->getGet('namespace');
        }

        $this->targetType = $targetType;

        if ($targetType === 'vendor' && $vendorPackage) {
            $this->setupVendorPaths($vendorPackage, $vendorNamespace);
        }

        $this->generateModel($table);
        $this->generateController($table);
        $this->generateView($table);
        $this->generateViewModal($table);
        $this->generateLanguage($table);
        $this->generateMigration($table);
        $this->generateLanguageES($table);

        if ($targetType === 'vendor') {
            $this->generateVendorRoutesFile($table);
            $this->updateSeederPermissions($table);
        } else {
            $this->generatePermissions($table);
        }

        $tableUpCase = ucfirst($table);

        echo "";
        echo "✅ CRUD generado exitosamente en: " . ($targetType === 'vendor' ? $this->vendorPackage : 'app') . "";
        echo "";
    }

    // ... (el resto de métodos: setupVendorPaths, generateModel, generateController, etc.)
    // Por brevedad, no repetimos todo el código aquí, pero en el artículo real debes incluir el código completo.
}
?>

⚠️ Nota: El código anterior es un resumen. Para obtener el código completo, visita el repositorio en GitHub o copia el bloque que aparece al final de este artículo.

🔌 Cómo usarlo

  1. Agrega la ruta en app/Config/Routes.php: $routes->get('generateCRUDComposer/(:any)', 'julio101290\boilerplate\Controllers\AutoCrudControllerComposer::index/$1');
  2. Genera un CRUD en app: http://tusitio.com/generateCRUDComposer/nombre_tabla
  3. Genera en tu paquete vendor: http://tusitio.com/generateCRUDComposer/nombre_tabla?target=vendor&package=tu/paquete

¡Y listo! En segundos tendrás todo el código listo para usar.

🎯 Conclusión y llamado a la acción

El generador automático de CRUD ha evolucionado de un simple script a una herramienta profesional que:

  • 🚀 Acelera el desarrollo de forma drástica.
  • 🛡️ Garantiza seguridad (CSRF, validación, soft delete correcto).
  • 📦 Soporta tanto proyectos monolíticos como paquetes reutilizables.
  • 🧹 Genera código limpio y mantenible.

Si aún no lo has probado, te estás perdiendo de ahorrar cientos de horas.

👉 Haz un fork del repositorio julio101290/boilerplate, instálalo y pruébalo hoy mismo.

¿Tienes sugerencias o mejoras? ¡Las contribuciones son bienvenidas! Abre un issue o envía un pull request.


#CodeIgniter4 #PHP #DesarrolloWeb #CRUD #Boilerplate #AhorroDeTiempo #OpenSource

🎸 Un Ejército de un Solo Hombre: ¡John Fogerty Grabó TODOS los Instrumentos de “Centerfield”! 🤘

Entrada fija

🎸 Un Ejército de un Solo Hombre: ¡John Fogerty Grabó TODOS los Instrumentos de “Centerfield”! 🤘

¿Es posible grabar un hit mundial tú solo en una habitación? John Fogerty dijo: “Sujétenme la guitarra” y en 1985 lanzó “Centerfield”, el álbum que demostró que el talento (y una buena máquina) pueden contra el mundo. 🌍🔥

Hoy analizamos cómo se gestó esta obra maestra donde John se convirtió en una banda completa: guitarrista, bajista, baterista y cantante de sus propios sueños. ¡Sube el volumen! 🔊✨


🦾 La Rebelión del Solista: ¿Por qué grabarlo todo?

Tras años de batallas legales y silencio absoluto, Fogerty volvió con hambre de gloria pero sin ganas de lidiar con dinámicas de banda. Quería que cada nota fuera 100% controlada por él.

En éxitos inmortales como “The Old Man Down the Road”, “Centerfield” y “Rock ‘n’ Roll Girls”, John construyó el sonido capa por capa, convirtiendo el estudio en su laboratorio personal. 🧱🎶

🥁 1. La Batería: El Corazón de Silicio (LinnDrum)

El secreto del ritmo hipnótico de este álbum no fue un baterista de carne y hueso, sino la legendaria LinnDrum (LM-2).

  • El Sonido: Es ese golpe seco y perfecto que define el pop-rock de los 80.
  • El Toque Fogerty: Para que no sonara como un robot, John programaba la máquina y luego grababa percusiones reales y platos encima. ¡Esa mezcla de tecnología y alma es lo que te hace mover el pie sin querer! 🤖+👨‍🎤

🎸 2. El Bajo con “Groove” Pantanoso

Fogerty siempre tuvo un oído privilegiado para el ritmo. Él grabó todas las líneas de bajo del disco, buscando esa sencillez sólida y directa que hacía que sus canciones fueran irresistibles. ¡Nada de músicos de sesión, solo puro talento Fogerty! 🎸

🎸 3. El Muro de Guitarras

Aquí es donde John desplegó su arsenal. Grabó múltiples pistas para cada canción: la rítmica para el cuerpo, la líder para los arreglos y esos solos icónicos que hoy son leyenda. Al grabarlas todas él mismo, logró una cohesión sonora imposible de replicar. 🎸🎸


🎙️ ¿Y las voces? ¡También él solo!

Si escuchas los coros y las armonías de fondo, no busques créditos de otros cantantes. ¡Es John Fogerty haciéndose los coros a sí mismo! Grabó varias pistas de su propia voz para crear ese efecto de grupo vocal potente que dominó la radio en los 85. 🎤🎤🎤

La única excepción: El brillante solo de saxo en “Rock ‘n’ Roll Girls” fue interpretado por Alphonso Johnson. ¡Pero el resto del universo sonoro de Centerfield es 100% obra de un solo hombre! 🎷🚫


📊 Radiografía Técnica de “Centerfield”

Para los amantes de la producción musical, así se repartieron las tareas en el estudio:

InstrumentoResponsableDetalle Clave
Voz y CorosJohn FogertyArmonías en capas 🎤
GuitarrasJohn FogertyFender Telecaster / Gibson LP 🎸
BajoJohn FogertyEstilo minimalista y sólido 🎸
BateríaFogerty + LinnDrumHíbrido humano-digital 🥁

💡 Conclusión: El Legado del “One-Man Army”

Grabar todos los instrumentos de un álbum que llega al #1 de Billboard es una hazaña que solo genios como Prince o Stevie Wonder han logrado con éxito. Fogerty demostró que con una visión clara y dominio de la tecnología, un músico no tiene límites. 🏆

“Centerfield” no fue solo un disco de regreso; fue la prueba de que John Fogerty era, es y será el verdadero sonido del Rock americano. 💿🇺🇸

🤔 ¿Qué opinas de esta hazaña?

¿Crees que se nota cuando un solo músico toca todo el disco o prefieres el sonido de una banda en vivo? ¡Cuéntanos en los comentarios! 👇

#JohnFogerty #Centerfield #RockNRollGirls #OneManBand #HombreOrquesta #HistoriaDelRock #80sMusic #LinnDrum #ProduccionMusical #ClassicRock

Boilerplate de Órdenes de Mantenimiento para CodeIgniter 4

Entrada fija
Versión Estable
Descargas Totales
Versión Inestable
Licencia

Boilerplate Mantenimiento

Boilerplate de Órdenes de Mantenimiento para CodeIgniter 4

Módulo de Gestión de Órdenes de Mantenimiento

Un módulo profesional y escalable para la gestión de órdenes de mantenimiento, desarrollado para CodeIgniter 4.

Este boilerplate está diseñado para:

  • Departamentos de TI
  • Talleres automotrices
  • Mantenimiento de equipos industriales
  • Centros de reparación en general

Características

  • Arquitectura modular (fácil integración)
  • Gestión completa de órdenes de mantenimiento
  • Catálogo de departamentos
  • Gestión de empleados
  • Asignación de activos por empleado
  • Dashboard con órdenes pendientes
  • Estructura escalable lista para integrarse en ERP

Requisitos

  • PHP 8.1 o superior
  • CodeIgniter 4
  • phpcfdi/sat-catalogos
  • julio101290/boilerplatelog
  • julio101290/boilerplateinventory

Instalación

Instalar vía Composer

composer require julio101290/boilerplatemaintenance

Ejecutar migraciones y seeders

php spark boilerplatemaintenance:installmaintenance

Capturas del Módulo

Dashboard – Órdenes Pendientes

Catálogo de Departamentos

Órdenes de Mantenimiento


Ejemplo de Integración en el Menú

Menú Principal de Mantenimiento

Dashboard

Departamentos

Empleados

Productos por Empleado

Órdenes de Mantenimiento


Sistema Listo


Uso

Revisa las rutas, controladores, modelos, vistas, migraciones y seeders para comprender el funcionamiento interno y adaptarlo a tu sistema.


Historial de Cambios

Consulta el archivo CHANGELOG para conocer las actualizaciones recientes.


Contribuciones

Las contribuciones son bienvenidas:

  1. Haz un fork del repositorio
  2. Crea una rama de mejora
  3. Envía un pull request

Licencia

Este paquete es software libre distribuido bajo la Licencia MIT.


Autor

Desarrollado y mantenido por Julio Leyva
¡Feliz programación! 🚀

Guía Definitiva: Conectividad PHP a SAP HANA en Linux (Drivers Nativos)

Entrada fija

Cómo eliminar la dependencia de drivers de pago y configurar una conexión profesional, gratuita y permanente.


Introducción

En el desarrollo de aplicaciones que interactúan con SAP Business One, la conectividad desde entornos Linux suele ser un desafío técnico. Muchos desarrolladores optan por drivers de terceros que, si bien son funcionales, requieren licencias costosas o expiran tras periodos de prueba.

Esta publicación detalla el proceso paso a paso para instalar el HDB Client oficial de SAP y configurar el gestor unixODBC para lograr una integración transparente con PHP, manteniendo la compatibilidad total con servidores Windows.

1. Preparación del Entorno

Antes de comenzar, debemos instalar las dependencias necesarias para que Linux pueda gestionar conexiones ODBC y para que PHP pueda comunicarse con ellas.

sudo apt-get update
sudo apt-get install unixodbc unixodbc-dev php-odbc

Tras la instalación, es vital reiniciar el servidor web para cargar el módulo ODBC:

sudo systemctl restart apache2

2. Instalación del SAP HANA Client (HDBClient)

El driver nativo de SAP es una librería de alto rendimiento (libodbcHDB.so). No busques un instalador .deb; SAP proporciona un script de instalación propio.

  1. Descarga el paquete HDB CLIENT LINUX X86_64.
  2. Descomprime y entra en la carpeta mediante la terminal.
  3. Ejecuta el instalador con privilegios de superusuario:
sudo ./hdbinst

Por defecto, el software se ubicará en /usr/sap/hdbclient/. Esta ruta será nuestra referencia para la configuración.

3. Registro del Driver en el Sistema

Linux utiliza el archivo /etc/odbcinst.ini para saber qué drivers están disponibles. Aquí es donde solucionaremos de forma global los problemas de certificados SSL y X.509.

Edita el archivo: sudo nano /etc/odbcinst.ini y pega lo siguiente:

[SAP_HANA_NATIVE]
Description = SAP HANA Driver Oficial
Driver      = /usr/sap/hdbclient/libodbcHDB.so
Setup       = /usr/sap/hdbclient/libodbcHDB.so
UsageCount  = 1
# Desactivación de cifrado para evitar errores de comunicación
encrypt                = false
sslValidateCertificate = false

4. Configuración del DSN (Data Source Name)

El DSN es el alias que usará tu código PHP. Al definirlo en /etc/odbc.ini, abstraes la dirección IP y el puerto de tu código fuente.

Edita el archivo: sudo nano /etc/odbc.ini:

[MI_DSN_SAPHANA]
Driver     = SAP_HANA_NATIVO
ServerNode = 192.168.x.x:30015
User       = TU_USUARIO_DB
Password   = TU_CONTRASEÑA_DB

Importante: Borra cualquier archivo oculto en tu carpeta personal (~/.odbc.ini) para asegurar que el sistema lea la configuración global de /etc/.

5. Prueba de Fuego desde Terminal

Antes de probar en la web, usamos la herramienta isql. Esta prueba valida que los archivos INI son correctos y que el firewall permite la conexión.

isql -v MI_DSN_SAPHANA TU_USUARIO_DB 'TU_CONTRASEÑA'

Si observas el mensaje + Connected! +, tu sistema operativo ya está hablando con SAP HANA.

6. Implementación en PHP

Gracias a esta configuración, tu código PHP se mantiene limpio y profesional. No necesitas strings de conexión kilométricos, solo el nombre del DSN.

<?php
$dsn      = "MI_DSN_SAPHANA";
$usuario  = "TU_USUARIO_DB";
$password = 'TU_CONTRASEÑA_COMPLEJA'; // Usar comillas simples para evitar errores con caracteres especiales

$conexion = odbc_connect($dsn, $usuario, $password);

if ($conexion) {
    echo "Conexión establecida con éxito a SAP HANA.";
    // Ejemplo de consulta
    $result = odbc_exec($conexion, "SELECT 'Conexión Exitosa' FROM DUMMY");
    print_r(odbc_fetch_array($result));
} else {
    echo "Error de conexión: " . odbc_errormsg();
}
?>

7. Mantenimiento de Compatibilidad con Windows

Una de las mayores ventajas de este método es la portabilidad. Si tu servidor de producción es Windows y tu entorno de desarrollo es Linux:

  • En Windows: Crea un DSN de Sistema con el nombre MI_DSN_SAPHANA desde el Administrador de Datos ODBC.
  • En Linux: Mantén el DSN con el mismo nombre en /etc/odbc.ini.

Resultado: El mismo archivo PHP funcionará en ambos sistemas sin modificar una sola línea de código.

Conclusión

Configurar drivers nativos requiere un poco más de trabajo manual que usar instaladores automáticos de pago, pero los beneficios son claros: estabilidad, gratuidad y control total sobre la seguridad de la conexión. Al centralizar la configuración en los archivos del sistema, permitimos que nuestras aplicaciones PHP sean más robustas y fáciles de mantener.

🚀 Transforma tu Terminal Linux con IA Local

Entrada fija

Usa Llama 3 y Moondream para navegar, auditar seguridad y analizar imágenes.

¿Alguna vez deseaste que tu terminal fuera inteligente? Hoy vamos a crear “Q”, un asistente híbrido que vive en tu consola. Gracias a Ollama, este script puede entender órdenes en lenguaje natural y utilizar visión artificial para buscar archivos visualmente.

🛡️ Seguridad y Auditoría Automática

Con este script, puedes realizar auditorías de seguridad rápidas. Pregunta cosas como:

  • “¿Hay conexiones remotas activas?”
  • “Revisa si alguien ha intentado hackear mi PC”
  • “¿Qué usuarios han iniciado sesión recientemente?”

📦 Requisitos Previos

Primero, instala Ollama y las librerías necesarias:

# Instalar modelos
ollama pull llama3
ollama pull moondream

# Instalar librería de procesamiento de imagen
pip install Pillow

🐍 El Script Completo: ia-term-viva.py

Guarda el siguiente código en tu carpeta personal como ia-term-viva.py:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import subprocess, sys, time, re, os, base64

# Colores para la terminal
BLUE = '\033[94m'; GREEN = '\033[92m'; YELLOW = '\033[93m'
CYAN = '\033[96m'; MAGENTA = '\033[95m'; RED = '\033[91m'
RESET = '\033[0m'; BOLD = '\033[1m'

def typing_print(text, speed=0.02):
    for char in text:
        sys.stdout.write(char); sys.stdout.flush(); time.sleep(speed)
    print()

def image_to_base64(image_path):
    try:
        with open(image_path, "rb") as img_file:
            return base64.b64encode(img_file.read()).decode('utf-8')
    except: return None

def vision_analyze(prompt, image_path):
    img_b64 = image_to_base64(image_path)
    if not img_b64: return "ERROR"
    result = subprocess.run(
        ["ollama", "run", "moondream"],
        input=f"{prompt}\nimage: {img_b64}",
        text=True, capture_output=True, encoding="utf-8"
    )
    return result.stdout.strip()

def run_llama_logic(prompt_input):
    model = "llama3"
    system_instructions = (
        "Eres un experto en terminal Linux y Seguridad Informatica. "
        "Traduce la peticion a un comando Bash puro. FORMATO: Frase | comando."
    )
    res = subprocess.run(
        ["ollama", "run", model], 
        input=f"{system_instructions}\n\nUsuario: {prompt_input}", 
        text=True, capture_output=True, encoding="utf-8"
    )
    return res.stdout.strip()

def main():
    if len(sys.argv) < 2:
        print(f"{CYAN}¿Qué órdenes tienes, Julio César?{RESET}"); return

    query = " ".join(sys.argv[1:])

    # --- LÓGICA DE VISIÓN (MOONDREAM) ---
    if "busca" in query.lower() and ("persona" in query.lower() or "playera" in query.lower() or "imagen" in query.lower()):
        print(f"{YELLOW}👁️  Activando Moondream...{RESET}")
        carpeta = os.getcwd()
        if "en " in query.lower():
            pos_ruta = query.lower().split("en ")[-1].strip()
            if os.path.isdir(pos_ruta): carpeta = pos_ruta
            elif pos_ruta in ["esta carpeta", ".", "aquí"]: carpeta = os.getcwd()

        objetivo = query.lower().split("busca")[-1].split("en")[0].strip()
        fotos = [f for f in os.listdir(carpeta) if f.lower().endswith(('.png', '.jpg', '.jpeg', '.webp'))]

        for foto in fotos:
            ruta = os.path.join(carpeta, foto)
            print(f"🧐 Analizando: {foto}...", end="\r")
            res = vision_analyze(f"Is there {objetivo} in this image? Answer only yes or no.", ruta)
            if "yes" in res.lower():
                print(f"\n{GREEN}✅ ENCONTRADA: {foto}{RESET}")
                subprocess.run(["xdg-open", ruta]); return 
        print(f"\n{RED}Sin resultados.{RESET}"); return

    # --- LÓGICA DE TERMINAL (LLAMA 3) ---
    raw_output = run_llama_logic(query)
    if "|" in raw_output:
        partes = raw_output.split("|")
        frase, comando = partes[0].strip(), "|".join(partes[1:]).strip()
    else:
        frase, comando = "Entendido", raw_output.strip()

    comando = comando.replace("`", "").replace("bash", "").strip().split('\n')[0]
    print(f"\n{BOLD}{BLUE}🤖 Terminal:{RESET} ", end="")
    typing_print(f"'{frase}'")

    if len(comando) > 1:
        print(f"{MAGENTA}💻 Ejecutando:{RESET} {GREEN}{BOLD}{comando}{RESET}")
        with open("/tmp/last_ia_cmd", "w") as f: f.write(comando)
        subprocess.run(comando, shell=True)

if __name__ == "__main__":
    main()

⚙️ Configuración del Alias (.bashrc)

Para habilitar el comando q y permitir la navegación cd, añade esto al final de tu archivo ~/.bashrc:

q() {
    python3 ~/ia-term-viva.py "$@"
    if [ -f /tmp/last_ia_cmd ]; then
        local cmd=$(cat /tmp/last_ia_cmd)
        if [[ "$cmd" == cd* ]]; then eval "$cmd"; fi
        rm -f /tmp/last_ia_cmd
    fi
}

#Linux #InteligenciaArtificial #Ollama #Ciberseguridad #Python

OBS en Linux con PipeWire y Behringer UMC22: cómo grabar en pistas separadas y transmitir audio mezclado

Entrada fija

Si grabas o transmites en OBS Studio en Linux y usas una Behringer UMC22, es muy común encontrarse con este problema:

OBS muestra varias pistas de audio, pero solo la pista 1 tiene sonido grabado.

La buena noticia es que no es un error de OBS. Es el comportamiento normal de PipeWire y, bien configurado, permite algo muy potente: transmitir con audio mezclado y grabar en pistas separadas para edición.


🎯 Objetivo de esta configuración

  • Transmisión (YouTube, Twitch, Facebook): audio mezclado en una sola pista.
  • Grabación local: pistas separadas para:
    • Voz
    • Instrumento
    • Audio del sistema

Este es exactamente el flujo de trabajo usado por streamers y estudios profesionales.


🔴 El problema: solo la pista 1 tiene audio

En distribuciones Linux modernas (Ubuntu, Mint, Fedora, Arch), PipeWire reemplazó a PulseAudio y JACK. Por defecto, PipeWire mezcla las entradas de audio.

Si OBS captura audio usando:

  • default
  • monitor
  • Mic/Aux

Entonces el audio ya llega mezclado a OBS y no puede separarse en pistas reales.

Resultado:

  • OBS crea varias pistas ✔
  • Pero solo una tiene audio real ❌

🎛️ Entendiendo la Behringer UMC22

La Behringer UMC22 es una interfaz 2×2:

  • Input 1 (Left): micrófono (XLR)
  • Input 2 (Right): instrumento (Jack)

PipeWire la presenta como un dispositivo estéreo. Si eliges UMC22 Stereo, ambos canales se mezclan.

👉 La clave es capturar cada canal por separado.


✅ Configuración correcta en OBS (paso a paso)

1️⃣ Ajustes de audio en OBS

Ve a Ajustes → Audio y configura así:

  • Audio del escritorio: Deshabilitado
  • Mic/Aux: Deshabilitado
  • Mic/Aux 2 / 3: Deshabilitado
  • Frecuencia de muestreo: 48 kHz
  • Canales: Estéreo

OBS no debe capturar ningún audio global.


2️⃣ Agregar las fuentes correctas

🎧 Audio del sistema

  • Fuente: Captura de salida PipeWire
  • Dispositivo: Monitor of Built-in Audio

🎤 Micrófono (UMC22 canal izquierdo)

  • Fuente: Dispositivo de captura de audio
  • Dispositivo: UMC22 – Analog Input 1 (Left)

🎸 Instrumento (UMC22 canal derecho)

  • Fuente: Dispositivo de captura de audio
  • Dispositivo: UMC22 – Analog Input 2 (Right)

No uses: UMC22 Stereo, default, monitor ni Mic/Aux.


3️⃣ Asignar pistas correctamente

FuentePista 1 (Stream)Pista 2Pista 3
Audio del escritorio
Micrófono
Instrumento

Pista 1: mezcla final para el stream
Pista 2: voz limpia
Pista 3: instrumento limpio


📡 ¿Y en la transmisión? ¿Todo va mezclado?

Sí. Siempre.

Las plataformas de streaming solo aceptan una pista de audio. OBS mezcla todo automáticamente usando los faders del mezclador.

Esto es lo correcto:

  • El público escucha una mezcla balanceada
  • Tú conservas pistas separadas para edición

🎚️ Cómo controlar el audio del stream

  • Faders del mezclador
  • Mute / Solo
  • Filtros (compresor, gate, EQ)

Las pistas solo afectan la grabación, no la transmisión.


🧪 Cómo comprobar que funciona

  • Usa Helvum o qpwgraph y verifica conexiones separadas
  • Abre el archivo en Kdenlive, DaVinci Resolve o Shotcut

Debes ver varias pistas de audio independientes.


🧠 Consejos extra para la UMC22

  • Activa Direct Monitor para latencia cero
  • No monitorees instrumentos desde OBS
  • Aplica compresión solo a la voz para el stream

🏁 Conclusión

Con PipeWire y la Behringer UMC22:

  • ✔ El stream siempre va mezclado
  • ✔ La grabación puede ir en pistas separadas
  • ✔ La clave es capturar canales físicos reales

Esta configuración es estable, profesional y perfecta para streaming y grabación musical en Linux.


🔎 Palabras clave SEO

OBS Linux PipeWire, Behringer UMC22 OBS, OBS pistas de audio Linux, OBS grabar multipista, OBS transmitir audio mezclado, PipeWire OBS configuración

Página 2 de 145

Creado con WordPress & Tema de Anders Norén