Commit Graph
58 Commits
Author SHA1 Message Date
Jordan DiazandClaude Fable 5.1 3565abdd88 fix(plugins): endurecer pluginLifecycle y sanear _removeFile
Tras la revision de seguridad de 273c3dd:
- pluginLifecycle exige POST con cuerpo JSON (token, tokenHash, event, plugin),
  como _saveFile: el token no viaja en la query string.
- La excepcion de un gancho se loguea entera en el servidor y al cliente va
  sin rutas absolutas.
- _removeFile (preexistente): la ruta la mandaba el cliente sin sanear y
  _deleteAll borraba cualquier cosa alcanzable, ../ incluido. Ahora en modo
  plugin solo una carpeta directa de pluginsdir y en modo fichero solo dentro
  de la raiz de la web; nunca la raiz ni pluginsdir, nunca fuera por symlink.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HPL66q9F33YZxbLPUT7KW8
2026-09-12 22:50:20 +00:00
Jordan DiazandClaude Fable 5.1 273c3dd781 feat(plugins): ganchos de ciclo de vida y checkpoints boot_plugins y post_variables
Fase 0 del instalador de plugins (docs/plugins/fase0-instalador-diseno.md del
repo de Forge). Un plugin puede llevar install.php, uninstall.php, enable.php,
disable.php y status.php; los ejecuta runPluginLifecycle() dentro de la web
por action_ws=pluginLifecycle, sea Forge o el CMS legacy quien instale.
showInstall() reutiliza el mismo nucleo (installPluginArtifacts) en vez de
duplicar install.sql/schemaPresets.

Checkpoints nuevos, sin efecto hasta que un plugin los declare:
- boot_plugins: en init.php, con la BD ya conectada.
- post_variables: tras cada require de lib/variables.php (slug.php, sesion.php,
  lib/recursos.php), una vez por peticion. Un plugin llega DESPUES del bloque
  de cache de variables.php y puede pisarlo.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HPL66q9F33YZxbLPUT7KW8
2026-09-12 22:40:42 +00:00
Jordan DiazandClaude Fable 5.1 462cba0505 feat(core): cms/lib/plugins/index.php entra en el core distribuible
Es el cargador de plugins (loadPlugins lo incluye desde common.php): codigo del
motor, no un plugin. Hasta ahora solo existia en el core_client del WS, que deja
de ser una copia a mano y pasa a ser un checkout de este repo, asi que tiene que
vivir aqui para seguir siendo distribuible. Las 94 webs locales llevan
exactamente este contenido (md5 ef8e54e2), asi que el rsync no cambia nada.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HPL66q9F33YZxbLPUT7KW8
2026-09-11 12:11:49 +00:00
Jordan DiazandClaude Opus 5 e938135869 feat(cache): las webs nuevas llaman al checkpoint cache
Anade la llamada a addPlugins("cache", ...) al final del bloque CACHE HANDLER,
para que el plugin `cache_handler` pueda gobernar la cache de una web recien
creada sin tener que editarle el variables.php a mano.

El orden es deliberado: el checkpoint va DESPUES del default cableado, asi el
plugin puede apagar tambien lo que se encienda arriba (cache.php fija los
flags de CocoDB explicitamente, no solo cuando activa).

`CocoDB::localCache()` se queda como estaba: es el comportamiento actual de
toda web nueva y quitarlo seria una regresion de rendimiento para quien no
tenga el plugin instalado. Sin plugin, la web se comporta igual que antes de
este commit.

Nota: `lib/variables.php` esta en los excludes del rsync de web-base
(server/handlers/acai.py), asi que esto NO toca ninguna web existente — solo
las que se creen a partir de ahora.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FsEzft5j5tZqxPdTdUXxLK
2026-09-06 13:33:40 +00:00
Jordan DiazandClaude Opus 5 e4ab0567df feat(correo): SMTP desde la configuracion del panel, y fuera el proxy
Dos cambios que van juntos porque atacan el mismo fallo: en las webs de Forge no
salia ni un correo.

1. LOS CAMPOS DEL PANEL AHORA SIRVEN PARA ALGO

   La tabla `configuracion` lleva desde siempre email_smtp, email_host,
   email_port, email_secure, email_username, email_password, email_from y
   email_from_name... y no los leia NADIE. Aparecian solo en el schema: el panel
   te dejaba rellenarlos, se guardaban en la BD, y el envio seguia usando los
   defaults de la clase —con la contrasena VACIA— o lo que hubiera a mano en
   lib/variables.php. Un formulario decorativo.

   `cargarSmtpDeConfiguracion()` los vuelca sobre la clase cuando el check
   email_smtp esta marcado y email_host tiene valor. Se llama desde send() antes
   de enviar, no solo desde send_email().

   El schema de `configuracion` es PER-WEB y no todas tienen esas columnas
   (villagrancanaria no las tiene): se lee con @ sobre el array, asi que una web
   sin ellas se comporta exactamente igual que antes.

2. FUERA EL PROXY

   send_email_coco_proxy() mandaba a cocosolution.com/?sendQuantumEmail=1. Se
   quita por dos motivos medidos hoy:

     - Ese endpoint RECHAZA TODO. Probados seis formatos (JSON crudo,
       form-urlencoded, anidado, y el payload cifrado por tres vias) y siempre
       contesta {"error":1,"message":"Need SMTP data and Params"}, incluso con
       smtp_data completo. Es decir: ninguna web de Forge ha podido enviar por
       ahi, y eso es anterior a los cambios de hoy.
     - No hace falta. El container alcanza los SMTP por su cuenta: verificado
       contra smtp.gmail.com:465 y :587 y smtp.ionos.es:465, con banner 220.
       PHPMailer conecta solo cuando $smtp = true.

   Se va con el encrypt(), que solo usaba el proxy y en una linea que ya estaba
   comentada dentro de esa misma funcion. Cero llamadas a ambas fuera de la
   clase, comprobado en los cuatro arboles.

Verificado en cdn-ecommerce: con el check marcado, la traza pasa de
"smtp=no host=smtp.gmail.com pass=VACIA" a "smtp=si host=<el del panel>", y
PHPMailer intenta la conexion. Falta solo poner credenciales de verdad.

OJO, queda pendiente: send_email() devuelve NULL siempre, tanto si entrega como
si no. Un fallo de envio es hoy indistinguible de un exito.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 20:34:05 +00:00
Jordan DiazandClaude Opus 5 3615d85bc0 Revert "fix(correo): un solo camino de envio, el directo, y fuera el proxy"
El proxy no era prescindible: el container de las webs de Forge NO tiene MTA.
`which sendmail mail postfix ssmtp msmtp exim4` no devuelve nada y
`sendmail_path` apunta a /usr/sbin/sendmail, que no existe. Con $smtp = false
PHPMailer cae a mail() de PHP y ahi no sale nada, en silencio.

Medido sobre las webs de Forge:

  26  configuran CocoEmail::$smtp = true propio  -> enviarian igual
  57  NO configuran SMTP                         -> se quedaban SIN correo

Entre esas 57 esta cdn-ecommerce, que es justo la web de pruebas. Se vuelve a
la version condicional: proxy cuando hay .acai (Forge/test) y directo cuando no
(legacy de produccion). Cubre los tres casos, porque el proxy reenvia con el
smtp_data que le llega en el payload, asi que las 26 con SMTP propio siguen
saliendo por su servidor.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 20:19:22 +00:00
Jordan DiazandClaude Opus 5 f279d7d1d3 fix(correo): un solo camino de envio, el directo, y fuera el proxy
El commit anterior dejaba el envio condicionado al entorno (proxy en local,
directo en produccion) para poder unificar el fichero entre los dos canales sin
cambiarle el comportamiento a nadie. Decision posterior: el proxy no hace falta,
asi que se adopta la version de core_client tal cual y se envia SIEMPRE directo.

Se van dos funciones con el, y ninguna deja huerfanos:

  send_email_coco_proxy()  POST a cocosolution.com/?sendQuantumEmail=1
  encrypt()                solo la usaba el proxy, y en una linea que ya estaba
                           comentada dentro de esa misma funcion

Comprobado antes de quitarlas: cero llamadas fuera de la clase en web-base, en
las 131 webs de Forge, en el CMS central y en core_client.

Con esto core_client y web-base quedan identicos: 0 ficheros de deriva fuera de
plugins/, que actualizacion.php se salta a proposito.

OJO al alcance: las webs de Forge pasan de enviar por el proxy a enviar
directo. Es el cambio que se buscaba, pero conviene confirmarlo con un envio
real desde una web local antes de darlo por bueno.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 20:14:38 +00:00
Jordan DiazandClaude Opus 5 ec203bc1e6 fix(core): converger las clases con core_client, que llevaban meses derivando
El core viaja por dos canales que deberian ser iguales y no lo eran:
core_client -> webs legacy via el CMS, y web-base -> webs de Forge. Nadie los
sincroniza, asi que el mismo arreglo se hacia dos veces, por separado y con
implementaciones distintas.

Medida la deriva: 5 ficheros de classes/ (el resto eran plugins/, que
actualizacion.php se salta a proposito).

  CocoDB.php, CocoDB_alias.php  <- de core_client
      Traen los guards `if (file_exists(...))`: si falta el alias o AcaiAPI,
      CocoDB se declara igual con la implementacion legacy. Es degradacion
      real para webs viejas y aqui no la teniamos. Se pierde la declaracion de
      CocoDB_old, que no referencia NADIE en ninguno de los cuatro arboles.

  AcaiAPI.php, CocoParser.php   -> ya iban a core_client desde aqui
      AcaiAPI resulto ser el MISMO codigo: 9252 tokens en ambos y 0
      diferencias al ordenarlos. El bloque de auditoria de updatedDate estaba
      colocado antes o despues del if($isInsert), y como ese bloque no escribe
      en $record, las dos posiciones son equivalentes.

  CocoEmail.php                 <- fusion, y la unica divergencia REAL
      web-base mandaba SIEMPRE por el proxy y core_client SIEMPRE directo, asi
      que el fichero no se podia unificar sin cambiarle el comportamiento a
      media plataforma. Ahora el camino se elige por entorno con el mismo
      criterio que header.php:81 (un .acai en la raiz = local), en linea y sin
      depender de otro fichero.

Para las webs de Forge NO cambia nada: las 131 tienen .acai salvo dos
directorios abandonados de abril sin index.php ni cms/lib/classes, que no
llegan a ser webs. Verificado en villagrancanaria: home, listado y ficha en
200 sin un solo error PHP, listado renderizando y hook en 54 ms.

Con los dos arboles ya identicos, el paso siguiente es que el CMS legacy se
nutra de web-base y dejar de mantener dos copias a mano.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 19:39:36 +00:00
Jordan DiazandClaude Opus 5 f81c5a26e1 fix(header): el entorno se detecta en linea, sin depender de otro fichero
Segundo intento. El primero (92f092d, revertido en 9a4906e) metia una funcion
`esEntornoLocal()` en `funciones.php` y la llamaba desde `header.php`. Tumbo
villagrancanaria.com con un Fatal error nada mas desplegarse.

POR QUE FALLO, QUE ES LO QUE HAY QUE RECORDAR

**Cada web puede tener PERSONALIZADO cualquier fichero del core, y el sync lo
respeta.** villagrancanaria tenia su propio `funciones.php` (38 KB frente a los
17 KB del nucleo: carga de modulos por AJAX, `dame_registros` en vez de
`CocoDB::get`, globales propias). Al sincronizar recibio el `header.php` nuevo
—que no estaba personalizado— y conservo su `funciones.php` viejo. Resultado:
`Call to undefined function esEntornoLocal()`, fatal, pagina entera caida.

Y lo hizo por el camino que no probe: las paginas de builder van por
`slug.php` -> `builder_saas/controlador.php` -> `header.php`, que NO pasa por
`funciones.php`. Yo verifique solo el camino de `index.php`.

Conclusion de diseño: **no se puede añadir una funcion a un fichero del core y
llamarla desde otro**. No hay garantia de que los dos lleguen a la misma web, y
cuando no llegan el fallo es FATAL, no degradado. Una linea duplicada es
preferible a una dependencia que el sync puede no entregar.

QUE HACE AHORA

`$esEntornoLocal = is_file(dirname(__FILE__) . "/.acai")`, en linea, sin funcion
compartida ni includes. La señal es la presencia del `.acai`: Forge lo escribe en
cada copia local y NUNCA viaja a produccion (esta en el `exclude` del manifest de
push-core y fuera de la allowlist del sync per-web). Se detecta LOCAL
explicitamente y todo lo demas es produccion: si la señal fallara, lo seguro es
respetar `pagina_publicada`.

   local                           -> siempre se ve
   prod + publicada                -> se ve
   prod + sin publicar + ?pruebas  -> se ve, con el cartel rojo
   prod + sin publicar + visitante -> mantenimiento

Sustituye a un `if (1)` hardcodeado que desactivaba la comprobacion entera, y al
`header.php.prod` que se habia empezado a mantener y que ADEMAS estaba MAL: al
comentar el `if(1){...}else{`, el cuerpo del else pasaba a ejecutarse siempre y un
visitante de una web PUBLICADA recibia `die(mantenimiento)`. Nadie lo vio porque
un `.prod` solo corre en produccion.

VERIFICADO sobre webs reales y por LOS DOS CAMINOS, incluido el del builder:
   builder    + local            -> 403238 bytes, la web
   builder    + prod publicada   -> 403238 bytes, la web
   index.php  + local            ->  78104 bytes, la web
   index.php  + prod sin publicar->   1943 bytes, mantenimiento
Cero fatal errors. Sintaxis con el PHP 8.3 de Plesk, el del hosting.

OJO AL DESPLEGAR: esto hace que la pagina de mantenimiento empiece a funcionar de
verdad. Hoy el `if (1)` la desactiva en todas las webs, produccion incluida, asi
que cualquier web con `pagina_publicada = 0` se esta viendo entera y pasara a
mostrar mantenimiento.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019gLX53DZJXGPFCf6DJTPWQ
2026-09-01 19:47:20 +00:00
Jordan Diaz 9a4906edfc Revert "fix(header): el entorno se detecta en tiempo de ejecucion, no con un fichero .prod"
This reverts commit 92f092dcc4.
2026-09-01 19:21:01 +00:00
Jordan DiazandClaude Opus 5 92f092dcc4 fix(header): el entorno se detecta en tiempo de ejecucion, no con un fichero .prod
`header.php` tenia un `if (1)` hardcodeado que se saltaba entera la comprobacion
de "pagina publicada". Para produccion se habia empezado a mantener una copia
`header.php.prod` que comentaba esas lineas con `#`.

ESA COPIA ESTABA MAL Y HABRIA TUMBADO TODAS LAS WEBS

Al comentar el `if(1){...}else{`, el cuerpo del `else` pasaba a ejecutarse SIEMPRE:

   visitante normal, web PUBLICADA -> die(mantenimiento)

Es decir, cualquier visitante sin sesion `?pruebas` recibia la pagina de
mantenimiento aunque la web estuviera publicada. Verificado ejecutando las dos
versiones. Nadie lo vio porque un `.prod` **solo corre en produccion**: no hay
forma de que ese fallo aparezca antes de estar desplegado.

Ese es el argumento de fondo contra el enfoque `.prod`, mas alla de la
duplicacion: el camino de produccion no se puede probar. Con la condicion en
tiempo de ejecucion se ejercita en local escondiendo el `.acai` un segundo — que
es como se ha verificado este commit.

LA CONDICION, AHORA EXPLICITA

   local                          -> siempre se ve (ahi no existe ni "publicada"
                                     ni ?pruebas: son conceptos de produccion)
   prod + publicada               -> se ve
   prod + sin publicar + ?pruebas -> se ve, con el cartel rojo
   prod + sin publicar + visitante-> mantenimiento

DONDE VIVE `esEntornoLocal()`, QUE COSTO TRES INTENTOS

La señal es la PRESENCIA del `.acai` en la raiz: Forge lo escribe en cada copia
local y NUNCA viaja a produccion (esta en el `exclude` del manifest de push-core
y fuera de la allowlist del sync per-web). Se detecta LOCAL explicitamente y todo
lo demas es produccion: si la señal fallara, el comportamiento seguro es respetar
`pagina_publicada`.

Va en `funciones.php` y no en:
  - `cms/`             -> se sobrescribe al actualizar el core desde el CMS legacy
  - `lib/variables.php`-> viaja en el sync PER-WEB (es el unico fichero de raiz que
                          lo hace), asi que cada web sobrescribe esa copia: una
                          funcion del nucleo ahi es la proxima divergencia
  - un plugin          -> `TokenGuard::isLocalTest()` hace esto mismo, pero el
                          nucleo no puede depender de que un plugin este cargado,
                          y builder_saas publica solo al editarlo

`funciones.php` es CORE (viaja en el manifest de push-core, como `header.php`) y
lo carga todo el front antes de usarlo.

VERIFICADO sobre una web real (cdn-ecommerce-base), no en un test aislado:
  con .acai    -> HTTP 200, 78104 bytes, la web entera, sin mantenimiento
  sin .acai    -> HTTP 200,  1943 bytes, pagina de mantenimiento
Sintaxis comprobada con el PHP 8.3 de Plesk, que es el del hosting.

OJO AL DESPLEGAR: esto hace que la pagina de mantenimiento EMPIECE A FUNCIONAR de
verdad. Hoy el `if (1)` la desactiva en todas las webs, produccion incluida, asi
que cualquier web con `pagina_publicada = 0` se esta viendo entera. Al llegar este
cambio, esas webs pasaran a mostrar mantenimiento — que es lo correcto, pero es un
cambio visible.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019gLX53DZJXGPFCf6DJTPWQ
2026-09-01 19:16:12 +00:00
Jordan Diaz 3747508812 Añadiendo el primer archivo .prod 2026-09-01 18:18:46 +00:00
Jordan Diaz 6397abfabb fix(AcaiAPI): fecha vacia, sellado de auditoria y escapado de ids
1) Un campo `date` con valor vacio rompia TODA insercion en la tabla.

   MySQL rechaza la cadena vacia en una columna datetime ("Incorrect datetime
   value") tanto si es NOT NULL como si es nullable, y bajo el sql_mode
   TRADITIONAL que fuerza Db.php no hay ningun valor "vacio" valido: '' falla,
   NULL falla si la columna es NOT NULL, '0000-00-00' falla por NO_ZERO_DATE y
   omitir la columna falla por no tener default. Bastaba UN campo fecha para que
   dejara de entrar cualquier registro, con un 400 "Error al insertar" sin
   detalle.

   El Admin clasico nunca se topa con esto porque ya lo resuelve:
   menus/default/save.php:287 pone la fecha actual cuando el campo no viene
   ("default date fields to current date"). AcaiAPI nunca replico esa regla —
   coerceValue tiene casos para checkbox, list y multitext, pero no para date.
   Ahora en INSERT hace lo mismo que el Admin; en UPDATE omite la columna, para
   no pisar con "hoy" un valor ya guardado solo porque venga la clave vacia.

   Se descarto la alternativa de crear la columna como NULL DEFAULT NULL: no
   arregla ninguna de las tablas ya existentes (el sync copia ficheros, no
   ejecuta ALTER) y ademas '' seguiria fallando igual.

2) Auditoria: `updatedDate`/`updatedByUserNum` pasan a respetar el valor que
   traiga el caller y generarse solo si no viene. Antes se pisaban siempre
   ("not overridable by the caller"). Alineado con la intencion del cambio de
   Anael en core_client, pero con los guards FUERA del if ($isInsert): dentro,
   un UPDATE se quedaba sin sellar la fecha porque no actuaba ninguna rama.
   No hay doble escritura: cuando el caller lo trae, lo escribe el bucle de
   campos y el guard evita que el bloque de auditoria lo repita en el SET.

3) Seguridad: los ids de las relaciones se escapan antes de interpolarlos en el
   IN (...). Salen del valor guardado de un campo `list`, que es un mediumtext
   sin validacion, asi que era una inyeccion SQL de segundo orden. Ya estaba
   corregido en core_client y faltaba aqui.

Ref: docs/escalados/estado.md 3.1 (repo acai-vscode-plugin)
2026-08-25 15:06:21 +00:00
Jordan Diaz 5861bac1da fix(security): sanear idioma de URL para cerrar XSS reflejado
El parametro ?idioma= llegaba crudo (la query string se reenvia entera a
slug.php via .htaccess) y se reflejaba sin escapar en RUTA_RAIZ/ROOT,
lang="..." y variables JS de las plantillas -> XSS reflejado explotable
en cualquier web (p.ej. ?idioma=';alert(document.cookie);// en contexto JS).

- variables.php: allowlist de idiomas antes de definir constantes/render;
  cualquier valor desconocido se descarta.
- footer.tpl/header.tpl: json_encode en los sinks JS (defensa en profundidad).
2026-07-29 11:48:05 +00:00
Jordan Diaz 46ecbb3f09 fix: tolerar null en $functions/$options de AcaiAPI (compat firma legacy CocoDB)
Los hooks legacy llaman CocoDB::updateRecords(..., null) y el nuevo
buildSetClause tipa $functions como array estricto: TypeError fatal en
PHP 8 (p.ej. pago por transferencia via IPNGenerica). Se normaliza en la
frontera publica (insert/update/get/delete).
2026-07-29 11:32:33 +00:00
Jordan Diaz 7d38e069cd Añadiendo las librerías de payments a sesion 2026-07-29 11:14:46 +00:00
Jordan Diaz 031ef33f9f fix: devolver valores como string en las lecturas (paridad mysql_* legacy)
PDO con prepares nativos devuelve tipos reales (depth=int 0) mientras que
mysql_* devolvia siempre strings. Los templates y codigo Acai comparan
estrictamente contra strings (is same as('0'), === '1'...) y con tipos
nativos esos filtros fallan en silencio — p.ej. el menu del header vacio
en webs importadas (impulse) porque el filtro depth is same as('0') no
matcheaba int(0). stringifyRows normaliza todos los escalares no-NULL a
string en los 5 paths de lectura que devuelven datos hacia fuera: get(),
uploads, cache de queries de relaciones, traducciones y pluginsConfig.
2026-07-22 11:14:55 +00:00
Jordan Diaz fe7e9ee2e6 fix: no inyectar campos de auditoria en tablas raw (ignoreSchema)
buildSetClause añadia siempre updatedDate/updatedByUserNum (y en inserts
createdDate/createdByUserNum), pero las tablas raw que se escriben con
ignoreSchema (aux_plg_config, uploads, traducciones...) no tienen esas
columnas y el INSERT/UPDATE moria con Unknown column — p.ej. guardar la
config de un plugin desde el dashboard fallaba con 'No se pudo guardar la
configuracion en la base de datos'. Se replica la semantica del CocoDB
legacy: prepareBaseSQL solo añadia auditoria y defaults cuando habia
schema cargado.
2026-07-22 09:40:13 +00:00
Jordan Diaz 34d1458f3a perf: self-provision the traducciones index required by batched t()
The batched translation preload requires the (tableName, recordNum,
prefix) index; without it the per-record query scans and is slower
than the legacy per-field lookup. t() now checks once per PHP process
(static) whether idx_tabla_registro_prefijo exists, creates it on the
fly when the DB user has ALTER rights (re-checking after a lost race),
and otherwise falls back to the legacy per-field query — never slower
than the original behavior on webs where the index cannot be created.

Verified live: dropped the index on villagrancanaria local, first
request recreated it automatically and subsequent renders stayed fast.
2026-07-21 18:34:10 +00:00
Jordan Diaz bcd21dd274 perf: batch per-record translation preload in t()
t() issued one SELECT per translated field (1,300+ queries on a typical
multilanguage home). It now preloads all translations of a (table,
record, language) in a single query and serves subsequent fields from
memory, preserving the previous LIMIT 1 semantics (first row per
fieldName). Measured on villagrancanaria: cold render -60% (en home)
/ -67% (es home), byte-identical output.

REQUIRES index (tableName, recordNum, prefix) on cms_traducciones —
without it the batched query scans and is SLOWER than before:
ALTER TABLE cms_traducciones ADD INDEX idx_tabla_registro_prefijo (tableName, recordNum, prefix);
2026-07-21 18:29:46 +00:00
Jordan Diaz 432130565b AcaiAPI data layer + CocoDB facade
AcaiAPI: PDO-based data access layer (prepared statements, JOIN relation
loading, batch upload fetching) developed on villagrancanaria.
CocoDB_alias declares the CocoDB facade delegating to AcaiAPI; the
legacy implementation stays available as CocoDB_old.
2026-07-21 17:29:51 +00:00
Jordan Diaz dd2657a5d3 CmsApi Libraries 2026-06-09 09:44:45 +00:00
Jordan 927888d0d9 Update CocoDB.php 2026-05-12 18:33:16 +01:00
Jordan 2471e9f3e1 Create cmsApiInit.js 2026-04-21 08:09:21 +01:00
Jordan a84eaff98b layout_header_footer 2026-04-20 22:40:47 +01:00
Jordan 130a84a093 dasdas 2026-04-15 20:00:09 +01:00
Jordan f64a790b9b dasdsa 2026-04-15 19:58:40 +01:00
Jordan 4f78c9276e detect 2026-04-15 19:56:57 +01:00
Jordan 6cd37bd2c1 Update funciones.php 2026-04-15 19:53:58 +01:00
Jordan e5948007da mas 2026-04-15 19:53:33 +01:00
Jordan bd1386890b error html 2026-04-15 19:49:59 +01:00
Jordan 7bb61b3691 Update slug.php 2026-04-15 19:28:57 +01:00
Jordan 628231a530 Update .htaccess 2026-04-11 16:33:46 +01:00
Jordan f4e1cb8135 Update .htaccess 2026-04-09 21:25:49 +01:00
Jordan 90f683ccf8 Update .htaccess 2026-04-09 21:24:36 +01:00
Jordan 407038c72f Update funciones.php 2026-04-09 18:32:48 +01:00
Jordan dfc5b7bc75 Update funciones.php 2026-04-09 18:28:54 +01:00
Jordan d64957cb6a Update footer.php 2026-04-09 18:15:11 +01:00
Jordan 9b627ae143 Quitando template/estandar/images 2026-04-08 13:31:51 +01:00
Jordan 8945934dff eliminando js 2026-03-31 14:02:56 +01:00
Jordan e69b25126a aliminamos esta carpeta de la base 2026-03-31 14:00:12 +01:00
Jordan 5f09794479 Update footer.php 2026-03-31 13:20:52 +01:00
Jordan 49db7b5701 Update footer.php 2026-03-31 13:19:27 +01:00
Jordan e50fc5deba Update footer.php 2026-03-31 12:56:46 +01:00
Jordan 3ff8e30b85 Update footer.php 2026-03-29 11:11:59 +01:00
Jordan a72755cffb update sesion.php 2026-03-22 13:13:40 +00:00
Jordan eb7ceef920 update sesion.php 2026-03-22 13:10:14 +00:00
Jordan 49ac4af10d update sesion.php 2026-03-06 18:11:52 +00:00
Jordan aaeeef2e0a update gitignore 2026-03-06 18:11:23 +00:00
Jordan 4e4bfde8a5 Quitando payments 2026-03-05 19:01:22 +00:00