$v) { if (is_string($v)) { $row[$k] = toUtf8($v, $srcEncoding); } } return $row; } /** * Limpia un VARCHAR de Firebird: recorta espacios y convierte encoding. */ function cleanStr(?string $val, string $srcEncoding = 'WIN1252'): ?string { if ($val === null) return null; $val = trim(toUtf8($val, $srcEncoding) ?? ''); return $val === '' ? null : $val; } /** * Convierte fecha Firebird (puede ser DATE o string 'YYYY-MM-DD') a MySQL DATE. * Devuelve null si la fecha es inválida o vacía. */ function fbDate($val): ?string { if ($val === null) return null; if ($val instanceof DateTime) return $val->format('Y-m-d'); $str = trim((string)$val); if ($str === '' || $str === '0000-00-00') return null; try { return (new DateTime($str))->format('Y-m-d'); } catch (\Exception $e) { return null; } } /** * INSERT en batch. * $pdo → conexión MySQL * $table → nombre de la tabla * $rows → array de arrays asociativos con los mismos keys * $ignore → usa INSERT IGNORE para saltar duplicados */ function batchInsert(PDO $pdo, string $table, array $rows, bool $ignore = false): int { if (empty($rows)) return 0; $cols = array_keys($rows[0]); $colList = implode(', ', array_map(fn($c) => "`$c`", $cols)); $phRow = '(' . implode(', ', array_fill(0, count($cols), '?')) . ')'; $inserted = 0; foreach (array_chunk($rows, 500) as $chunk) { $placeholders = implode(', ', array_fill(0, count($chunk), $phRow)); $keyword = $ignore ? 'INSERT IGNORE' : 'INSERT'; $sql = "$keyword INTO `$table` ($colList) VALUES $placeholders"; $flat = array_merge(...array_map('array_values', $chunk)); $stmt = $pdo->prepare($sql); $stmt->execute($flat); $inserted += $stmt->rowCount(); } return $inserted; } /** Escribe al log y a stdout simultáneamente. */ function etlLog(string $msg, $logFp = null): void { $line = '[' . date('H:i:s') . '] ' . $msg . PHP_EOL; echo $line; if ($logFp) fwrite($logFp, $line); } /** Devuelve conteo de filas de una tabla Firebird. */ function fbCount(PDO $fb, string $table): int { return (int) $fb->query("SELECT COUNT(*) FROM $table")->fetchColumn(); }